From 81847fbbcc66e215c76bf745923836a75631e34f Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 5 Aug 2019 09:27:51 -0400 Subject: [PATCH 01/57] Land on Blogs page when SimpleNav is enabled This shows the Blogs page instead of the Editor to logged in users on the `/` path when the new `simple_nav` config option is enabled. Ref T680 --- app.go | 7 ++++++- config/config.go | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app.go b/app.go index 1fa6f8a..6d6a689 100644 --- a/app.go +++ b/app.go @@ -193,7 +193,12 @@ func handleViewHome(app *App, w http.ResponseWriter, r *http.Request) error { // Show correct page based on user auth status and configured landing path u := getUserSession(app, r) if u != nil { - // User is logged in, so show the Pad + // User is logged in, so show the Pad or Blogs page, depending on config + if app.cfg.App.SimpleNav { + // Simple nav, so home page is Blogs page + return viewCollections(app, u, w, r) + } + // Default config, so home page is editor return handleViewPad(app, w, r) } diff --git a/config/config.go b/config/config.go index f46282e..d026a74 100644 --- a/config/config.go +++ b/config/config.go @@ -68,6 +68,7 @@ type ( JSDisabled bool `ini:"disable_js"` WebFonts bool `ini:"webfonts"` Landing string `ini:"landing"` + SimpleNav bool `ini:"simple_nav"` // Users SingleUser bool `ini:"single_user"` From 90ad50c7f5c30f45065798304cf7708a47ac9342 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 5 Aug 2019 09:34:47 -0400 Subject: [PATCH 02/57] Use normal nav on user pages when SimpleNav This shows About, Reader, Log out links on backend user pages when logged in. It also adds "New post" buttons on the backend pages and blogs. --- less/core.less | 25 +++++++++++++++++++++++++ templates/base.tmpl | 2 +- templates/collection.tmpl | 1 + templates/user/include/header.tmpl | 23 ++++++++++++++++++++--- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/less/core.less b/less/core.less index 7e39f35..b1b8301 100644 --- a/less/core.less +++ b/less/core.less @@ -405,6 +405,31 @@ body { } } +nav#full-nav { + margin: 0; + + .left-side { + display: inline-block; + + a:first-child { + margin-left: 0; + } + } + + .right-side { + float: right; + } +} + +nav#full-nav a.simple-btn { + font-family: @sansFont; + border: 1px solid #ccc !important; + padding: .5rem 1rem; + margin: 0; + .rounded(.25em); + text-decoration: none; +} + .post-title { a { &:link { diff --git a/templates/base.tmpl b/templates/base.tmpl index 775dac9..c1f17ad 100644 --- a/templates/base.tmpl +++ b/templates/base.tmpl @@ -19,7 +19,7 @@ {{end}} diff --git a/templates/collection.tmpl b/templates/collection.tmpl index 18942a9..364ba15 100644 --- a/templates/collection.tmpl +++ b/templates/collection.tmpl @@ -48,6 +48,7 @@ {{else}}
  • {{.SiteName}}
  • {{end}} + {{if .SimpleNav}}
  • New Post
  • {{end}}
  • Customize
  • Stats

  • diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index 312d0b8..93020e2 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -38,7 +38,13 @@ {{else}} -

    {{.SiteName}}

    + {{ if .SimpleNav }} + {{end}} {{end}}
    From 1d25784d20537923889eb8a36d7efc2cfbdace32 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 5 Aug 2019 09:54:05 -0400 Subject: [PATCH 03/57] Add `bare` editor option This adds a new editor template that strips away most of the customization features in the default editor and includes only: - publishing - editing - viewing word count It also restricts publishing to a user's first collection, so it's optimized for instances that only allow users to have a single collection and don't use Drafts. Ref T680 T677 --- less/core.less | 2 +- less/pad-theme.less | 4 +- less/pad.less | 7 ++ templates/bare.tmpl | 235 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 templates/bare.tmpl diff --git a/less/core.less b/less/core.less index b1b8301..f4332a9 100644 --- a/less/core.less +++ b/less/core.less @@ -421,7 +421,7 @@ nav#full-nav { } } -nav#full-nav a.simple-btn { +nav#full-nav a.simple-btn, .tool button { font-family: @sansFont; border: 1px solid #ccc !important; padding: .5rem 1rem; diff --git a/less/pad-theme.less b/less/pad-theme.less index af1f95c..a8f668e 100644 --- a/less/pad-theme.less +++ b/less/pad-theme.less @@ -63,7 +63,7 @@ body#pad, body#pad-sub { } } #belt { - a { + a, button { color: #000; } } @@ -100,7 +100,7 @@ body#pad, body#pad-sub { } } #belt { - a { + a, button { color: white; } } diff --git a/less/pad.less b/less/pad.less index d37c6bc..a132b30 100644 --- a/less/pad.less +++ b/less/pad.less @@ -222,6 +222,13 @@ body#pad, body#pad-sub { font-style: italic; } } + button { + font-family: @sansFont; + background-color: transparent; + padding-top: 0.25rem; + padding-bottom: 0.25rem; + border: 0; + } } } } diff --git a/templates/bare.tmpl b/templates/bare.tmpl new file mode 100644 index 0000000..1398a47 --- /dev/null +++ b/templates/bare.tmpl @@ -0,0 +1,235 @@ +{{define "pad"}} + + + + {{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} — {{.SiteName}} + + + + + + + + +
    + + + +
    +
    + {{if not .SingleUser}}

    {{.SiteName}}

    {{end}} + + +
    + +
    + {{if .Editing}}{{end}} +
    +
    +
    + + + + +{{end}} From 17f7bc1becd321810694105535b5bc9ce892ddac Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 6 Aug 2019 09:15:05 -0400 Subject: [PATCH 04/57] Move user navigation to its own template section Ref T681 --- templates/user/include/header.tmpl | 37 ++++++++++++++++-------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index 93020e2..5f29646 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -1,20 +1,4 @@ -{{define "header"}} - - - - - {{.PageTitle}} {{if .Separator}}{{.Separator}}{{else}}—{{end}} {{.SiteName}} - - - - - - - - - - - +{{define "user-navigation"}} {{if .SingleUser}} {{else}} - {{ if .SimpleNav }} - {{if .SimpleNav}}{{if .Username}}
    + {{if .Chorus}}{{if .Username}}{{end}} From 3c104cb3aa3c1164e0337ea11cc7c41dbd5643e8 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 9 Aug 2019 11:31:42 -0700 Subject: [PATCH 18/57] check for lessc executable in any location previously the checks were explicit locations which does not work when using something like nvm to manage node packages and versions. this checks for the executable and sets the script variable LESSC to the full path of the one found. if none was found the make command will error. --- less/Makefile | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/less/Makefile b/less/Makefile index e81258a..5e51b4d 100644 --- a/less/Makefile +++ b/less/Makefile @@ -1,13 +1,8 @@ -ifeq ($(shell which lessc),/usr/bin/lessc) - LESSC=/usr/bin/lessc -else ifeq ($(shell which lessc),/usr/local/bin/lessc) - LESSC=/usr/local/bin/lessc -else ifeq ($(shell which lessc),/bin/lessc) - LESSC=/bin/lessc -else - LESSC=node_modules/.bin/lessc +LESSC := $(shell command -v lessc 2> /dev/null) + +ifndef LESSC + $(error "less is not installed, please run install-less.sh") endif -export LESSC CSSDIR=../static/css/ From d8405680b4e1720a020b86a94a4684700d0c2e9c Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 9 Aug 2019 14:57:09 -0400 Subject: [PATCH 19/57] Respect `private` setting with home page Reader Ref T681 --- app.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app.go b/app.go index 8b630df..1e24c75 100644 --- a/app.go +++ b/app.go @@ -193,16 +193,20 @@ func handleViewHome(app *App, w http.ResponseWriter, r *http.Request) error { return handleViewCollection(app, w, r) } - if app.cfg.App.Chorus { - // This instance is focused on reading, so show Reader on home route - return viewLocalTimeline(app, w, r) - } - // Multi-user instance forceLanding := r.FormValue("landing") == "1" if !forceLanding { // Show correct page based on user auth status and configured landing path u := getUserSession(app, r) + + if app.cfg.App.Chorus { + // This instance is focused on reading, so show Reader on home route if not + // private or a private-instance user is logged in. + if !app.cfg.App.Private || u != nil { + return viewLocalTimeline(app, w, r) + } + } + if u != nil { // User is logged in, so show the Pad return handleViewPad(app, w, r) From 047ad0323b12c702cef5ebcfde3af88fa0c3d610 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 9 Aug 2019 14:58:17 -0400 Subject: [PATCH 20/57] Don't show user pages in nav when unauth'd Ref T681 T680 --- templates/base.tmpl | 2 ++ templates/user/include/header.tmpl | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/templates/base.tmpl b/templates/base.tmpl index 678ada8..f8b66c8 100644 --- a/templates/base.tmpl +++ b/templates/base.tmpl @@ -41,9 +41,11 @@ {{ end }} About {{ if not .SingleUser }} + {{ if .Username }} {{if gt .MaxBlogs 1}}Blogs{{end}} {{if and (and .Chorus (eq .MaxBlogs 1)) .Username}}My Posts{{end}} {{if not .DisableDrafts}}Drafts{{end}} + {{ end }} {{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}Reader{{end}} {{if and (and .Chorus .OpenRegistration) (not .Username)}}Sign up{{end}} {{if not .Username}}Log in{{else if .SimpleNav}}Log out{{end}} diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index 5923c83..9d5bb54 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -50,9 +50,11 @@ {{ end }} About {{ if not .SingleUser }} + {{ if .Username }} {{if gt .MaxBlogs 1}}Blogs{{end}} - {{if and (and .Chorus (eq .MaxBlogs 1)) .Username}}My Posts{{end}} + {{if and .Chorus (eq .MaxBlogs 1)}}My Posts{{end}} {{if not .DisableDrafts}}Drafts{{end}} + {{ end }} {{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}Reader{{end}} {{if and (and .Chorus .OpenRegistration) (not .Username)}}Sign up{{end}} {{if .Username}}Log out{{else}}Log in{{end}} From 95a98234eb9dc3fe893515673543b7b759fb558f Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 9 Aug 2019 14:04:15 -0700 Subject: [PATCH 21/57] fix panic on duplicate remoteuser key this changes handleFetchCollectionInbox to log _all_ errors after attempting to insert an actor in the remoteusers table. previously checking for all errors _except_ duplicate keys would cause a panic if an actor made a request to follow while already having followed. --- activitypub.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/activitypub.go b/activitypub.go index 997609d..d47a7ea 100644 --- a/activitypub.go +++ b/activitypub.go @@ -375,11 +375,11 @@ func handleFetchCollectionInbox(app *App, w http.ResponseWriter, r *http.Request // Add follower locally, since it wasn't found before res, err := t.Exec("INSERT INTO remoteusers (actor_id, inbox, shared_inbox) VALUES (?, ?, ?)", fullActor.ID, fullActor.Inbox, fullActor.Endpoints.SharedInbox) if err != nil { - if !app.db.isDuplicateKeyErr(err) { - t.Rollback() - log.Error("Couldn't add new remoteuser in DB: %v\n", err) - return - } + // if duplicate key, res will be nil and panic on + // res.LastInsertId below + t.Rollback() + log.Error("Couldn't add new remoteuser in DB: %v\n", err) + return } followerID, err = res.LastInsertId() From b373aad29874384d6d4d58f14019387d8ebc7758 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 12 Aug 2019 09:58:30 -0700 Subject: [PATCH 22/57] prevent future posts from showing in pins this changes GetPinnedPosts to accept an includeFutre bool, which returns future dated pinned posts when true. --- collections.go | 14 ++++++++++++-- database.go | 12 +++++++++--- go.mod | 2 +- posts.go | 6 +++++- 4 files changed, 27 insertions(+), 7 deletions(-) diff --git a/collections.go b/collections.go index aee74f7..7eb3741 100644 --- a/collections.go +++ b/collections.go @@ -769,6 +769,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro displayPage.Collections = pubColls } } + isOwner := owner != nil if owner == nil { // Current user doesn't own collection; retrieve owner information owner, err = app.db.GetUserByID(coll.OwnerID) @@ -782,7 +783,11 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro // Add more data // TODO: fix this mess of collections inside collections - displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj) + if isOwner { + displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, true) + } else { + displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, false) + } err = templates["collection"].ExecuteTemplate(w, "collection", displayPage) if err != nil { @@ -866,6 +871,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e displayPage.Collections = pubColls } } + isOwner := owner != nil if owner == nil { // Current user doesn't own collection; retrieve owner information owner, err = app.db.GetUserByID(coll.OwnerID) @@ -878,7 +884,11 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e coll.Owner = displayPage.Owner // Add more data // TODO: fix this mess of collections inside collections - displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj) + if isOwner { + displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, true) + } else { + displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, false) + } err = templates["collection-tags"].ExecuteTemplate(w, "collection-tags", displayPage) if err != nil { diff --git a/database.go b/database.go index 34c5234..d3ff338 100644 --- a/database.go +++ b/database.go @@ -94,7 +94,7 @@ type writestore interface { UpdatePostPinState(pinned bool, postID string, collID, ownerID, pos int64) error GetLastPinnedPostPos(collID int64) int64 - GetPinnedPosts(coll *CollectionObj) (*[]PublicPost, error) + GetPinnedPosts(coll *CollectionObj, includeFuture bool) (*[]PublicPost, error) RemoveCollectionRedirect(t *sql.Tx, alias string) error GetCollectionRedirect(alias string) (new string) IsCollectionAttributeOn(id int64, attr string) bool @@ -1533,9 +1533,15 @@ func (db *datastore) GetLastPinnedPostPos(collID int64) int64 { return lastPos.Int64 } -func (db *datastore) GetPinnedPosts(coll *CollectionObj) (*[]PublicPost, error) { +func (db *datastore) GetPinnedPosts(coll *CollectionObj, includeFuture bool) (*[]PublicPost, error) { // FIXME: sqlite-backed instances don't include ellipsis on truncated titles - rows, err := db.Query("SELECT id, slug, title, "+db.clip("content", 80)+", pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL ORDER BY pinned_position ASC", coll.ID) + rows := &sql.Rows{} + var err error + if includeFuture { + rows, err = db.Query("SELECT id, slug, title, "+db.clip("content", 80)+", pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL ORDER BY pinned_position ASC", coll.ID) + } else { + rows, err = db.Query("SELECT id, slug, title, "+db.clip("content", 80)+", pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL AND created <= "+db.now()+" ORDER BY pinned_position ASC", coll.ID) + } if err != nil { log.Error("Failed selecting pinned posts: %v", err) return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve pinned posts."} diff --git a/go.mod b/go.mod index cc5fc57..5e040ba 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/writeas/slug v1.2.0 github.com/writeas/web-core v1.0.0 github.com/writefreely/go-nodeinfo v1.2.0 - golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f // indirect + golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 // indirect golang.org/x/sys v0.0.0-20190209173611-3b5209105503 // indirect diff --git a/posts.go b/posts.go index 2f3606f..edef6fb 100644 --- a/posts.go +++ b/posts.go @@ -1380,7 +1380,11 @@ Are you sure it was ever here?`, IsCustomDomain: cr.isCustomDomain, IsFound: postFound, } - tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll) + if p.IsOwner { + tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll, true) + } else { + tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll, false) + } tp.IsPinned = len(*tp.PinnedPosts) > 0 && PostsContains(tp.PinnedPosts, p) if !postFound { From ca957c4b6d59a1d32eae911ba30571d064d53b61 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 12 Aug 2019 12:35:17 -0700 Subject: [PATCH 23/57] fix missing collection hostname GetCollections and GetPublishableCollections now take a hostname parameter to allow setting the collecion hostname. All collections used in memory now have their hostname set. --- account.go | 21 +++++++++++---------- admin.go | 11 ++++++----- collections.go | 6 ++++-- database.go | 11 ++++++----- export.go | 2 +- go.mod | 2 +- pad.go | 7 ++++--- postrender.go | 12 +++++++----- 8 files changed, 40 insertions(+), 32 deletions(-) diff --git a/account.go b/account.go index 1cf259b..c69e2fe 100644 --- a/account.go +++ b/account.go @@ -13,6 +13,13 @@ package writefreely import ( "encoding/json" "fmt" + "html/template" + "net/http" + "regexp" + "strings" + "sync" + "time" + "github.com/gorilla/mux" "github.com/gorilla/sessions" "github.com/guregu/null/zero" @@ -22,12 +29,6 @@ import ( "github.com/writeas/web-core/log" "github.com/writeas/writefreely/author" "github.com/writeas/writefreely/page" - "html/template" - "net/http" - "regexp" - "strings" - "sync" - "time" ) type ( @@ -546,7 +547,7 @@ func getVerboseAuthUser(app *App, token string, u *User, verbose bool) *AuthUser if err != nil { log.Error("Login: Unable to get user posts: %v", err) } - colls, err := app.db.GetCollections(u) + colls, err := app.db.GetCollections(u, app.cfg.App.Host) if err != nil { log.Error("Login: Unable to get user collections: %v", err) } @@ -716,7 +717,7 @@ func viewMyCollectionsAPI(app *App, u *User, w http.ResponseWriter, r *http.Requ return ErrBadRequestedType } - p, err := app.db.GetCollections(u) + p, err := app.db.GetCollections(u, app.cfg.App.Host) if err != nil { return err } @@ -739,7 +740,7 @@ func viewArticles(app *App, u *User, w http.ResponseWriter, r *http.Request) err log.Error("unable to fetch flashes: %v", err) } - c, err := app.db.GetPublishableCollections(u) + c, err := app.db.GetPublishableCollections(u, app.cfg.App.Host) if err != nil { log.Error("unable to fetch collections: %v", err) } @@ -762,7 +763,7 @@ func viewArticles(app *App, u *User, w http.ResponseWriter, r *http.Request) err } func viewCollections(app *App, u *User, w http.ResponseWriter, r *http.Request) error { - c, err := app.db.GetCollections(u) + c, err := app.db.GetCollections(u, app.cfg.App.Host) if err != nil { log.Error("unable to fetch collections: %v", err) return fmt.Errorf("No collections") diff --git a/admin.go b/admin.go index fe19ad5..a27a068 100644 --- a/admin.go +++ b/admin.go @@ -13,16 +13,17 @@ package writefreely import ( "database/sql" "fmt" + "net/http" + "runtime" + "strconv" + "time" + "github.com/gogits/gogs/pkg/tool" "github.com/gorilla/mux" "github.com/writeas/impart" "github.com/writeas/web-core/auth" "github.com/writeas/web-core/log" "github.com/writeas/writefreely/config" - "net/http" - "runtime" - "strconv" - "time" ) var ( @@ -195,7 +196,7 @@ func handleViewAdminUser(app *App, u *User, w http.ResponseWriter, r *http.Reque p.LastPost = lp.Format("January 2, 2006, 3:04 PM") } - colls, err := app.db.GetCollections(p.User) + colls, err := app.db.GetCollections(p.User, app.cfg.App.Host) if err != nil { return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("Could not get user's collections: %v", err)} } diff --git a/collections.go b/collections.go index aee74f7..997d4d7 100644 --- a/collections.go +++ b/collections.go @@ -724,6 +724,8 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro return err } + c.hostName = app.cfg.App.Host + // Serve ActivityStreams data now, if requested if strings.Contains(r.Header.Get("Accept"), "application/activity+json") { ac := c.PersonObject() @@ -762,7 +764,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro owner = u displayPage.CanPin = true - pubColls, err := app.db.GetPublishableCollections(owner) + pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host) if err != nil { log.Error("unable to fetch collections: %v", err) } @@ -859,7 +861,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e owner = u displayPage.CanPin = true - pubColls, err := app.db.GetPublishableCollections(owner) + pubColls, err := app.db.GetPublishableCollections(owner, app.cfg.App.Host) if err != nil { log.Error("unable to fetch collections: %v", err) } diff --git a/database.go b/database.go index 34c5234..c980225 100644 --- a/database.go +++ b/database.go @@ -65,8 +65,8 @@ type writestore interface { ChangeSettings(app *App, u *User, s *userSettings) error ChangePassphrase(userID int64, sudo bool, curPass string, hashedPass []byte) error - GetCollections(u *User) (*[]Collection, error) - GetPublishableCollections(u *User) (*[]Collection, error) + GetCollections(u *User, hostName string) (*[]Collection, error) + GetPublishableCollections(u *User, hostName string) (*[]Collection, error) GetMeStats(u *User) userMeStats GetTotalCollections() (int64, error) GetTotalPosts() (int64, error) @@ -1559,7 +1559,7 @@ func (db *datastore) GetPinnedPosts(coll *CollectionObj) (*[]PublicPost, error) return &posts, nil } -func (db *datastore) GetCollections(u *User) (*[]Collection, error) { +func (db *datastore) GetCollections(u *User, hostName string) (*[]Collection, error) { rows, err := db.Query("SELECT id, alias, title, description, privacy, view_count FROM collections WHERE owner_id = ? ORDER BY id ASC", u.ID) if err != nil { log.Error("Failed selecting from collections: %v", err) @@ -1575,6 +1575,7 @@ func (db *datastore) GetCollections(u *User) (*[]Collection, error) { log.Error("Failed scanning row: %v", err) break } + c.hostName = hostName c.URL = c.CanonicalURL() c.Public = c.IsPublic() @@ -1588,8 +1589,8 @@ func (db *datastore) GetCollections(u *User) (*[]Collection, error) { return &colls, nil } -func (db *datastore) GetPublishableCollections(u *User) (*[]Collection, error) { - c, err := db.GetCollections(u) +func (db *datastore) GetPublishableCollections(u *User, hostName string) (*[]Collection, error) { + c, err := db.GetCollections(u, hostName) if err != nil { return nil, err } diff --git a/export.go b/export.go index 47a2603..86855e2 100644 --- a/export.go +++ b/export.go @@ -104,7 +104,7 @@ func compileFullExport(app *App, u *User) *ExportUser { User: u, } - colls, err := app.db.GetCollections(u) + colls, err := app.db.GetCollections(u, app.cfg.App.Host) if err != nil { log.Error("unable to fetch collections: %v", err) } diff --git a/go.mod b/go.mod index cc5fc57..5e040ba 100644 --- a/go.mod +++ b/go.mod @@ -63,7 +63,7 @@ require ( github.com/writeas/slug v1.2.0 github.com/writeas/web-core v1.0.0 github.com/writefreely/go-nodeinfo v1.2.0 - golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f // indirect + golang.org/x/crypto v0.0.0-20190208162236-193df9c0f06f golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 // indirect golang.org/x/sys v0.0.0-20190209173611-3b5209105503 // indirect diff --git a/pad.go b/pad.go index 1545b4f..b806615 100644 --- a/pad.go +++ b/pad.go @@ -11,12 +11,13 @@ package writefreely import ( + "net/http" + "strings" + "github.com/gorilla/mux" "github.com/writeas/impart" "github.com/writeas/web-core/log" "github.com/writeas/writefreely/page" - "net/http" - "strings" ) func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error { @@ -47,7 +48,7 @@ func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error { } var err error if appData.User != nil { - appData.Blogs, err = app.db.GetPublishableCollections(appData.User) + appData.Blogs, err = app.db.GetPublishableCollections(appData.User, app.cfg.App.Host) if err != nil { log.Error("Unable to get user's blogs for Pad: %v", err) } diff --git a/postrender.go b/postrender.go index af715be..93c2089 100644 --- a/postrender.go +++ b/postrender.go @@ -12,17 +12,18 @@ package writefreely import ( "fmt" - "github.com/microcosm-cc/bluemonday" - stripmd "github.com/writeas/go-strip-markdown" - "github.com/writeas/saturday" - "github.com/writeas/web-core/stringmanip" - "github.com/writeas/writefreely/parse" "html" "html/template" "regexp" "strings" "unicode" "unicode/utf8" + + "github.com/microcosm-cc/bluemonday" + stripmd "github.com/writeas/go-strip-markdown" + blackfriday "github.com/writeas/saturday" + "github.com/writeas/web-core/stringmanip" + "github.com/writeas/writefreely/parse" ) var ( @@ -36,6 +37,7 @@ var ( func (p *Post) formatContent(c *Collection, isOwner bool) { baseURL := c.CanonicalURL() + // TODO: redundant if !isSingleUser { baseURL = "/" + c.Alias + "/" } From f241d694258fd1c501249a09a00930b86be35d51 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 12 Aug 2019 14:12:35 -0700 Subject: [PATCH 24/57] reduce GetPinnedPosts calls to single line --- collections.go | 12 ++---------- posts.go | 6 +----- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/collections.go b/collections.go index 7eb3741..adf89d4 100644 --- a/collections.go +++ b/collections.go @@ -783,11 +783,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro // Add more data // TODO: fix this mess of collections inside collections - if isOwner { - displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, true) - } else { - displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, false) - } + displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner) err = templates["collection"].ExecuteTemplate(w, "collection", displayPage) if err != nil { @@ -884,11 +880,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e coll.Owner = displayPage.Owner // Add more data // TODO: fix this mess of collections inside collections - if isOwner { - displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, true) - } else { - displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, false) - } + displayPage.PinnedPosts, _ = app.db.GetPinnedPosts(coll.CollectionObj, isOwner) err = templates["collection-tags"].ExecuteTemplate(w, "collection-tags", displayPage) if err != nil { diff --git a/posts.go b/posts.go index edef6fb..a1383fa 100644 --- a/posts.go +++ b/posts.go @@ -1380,11 +1380,7 @@ Are you sure it was ever here?`, IsCustomDomain: cr.isCustomDomain, IsFound: postFound, } - if p.IsOwner { - tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll, true) - } else { - tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll, false) - } + tp.PinnedPosts, _ = app.db.GetPinnedPosts(coll, p.IsOwner) tp.IsPinned = len(*tp.PinnedPosts) > 0 && PostsContains(tp.PinnedPosts, p) if !postFound { From 55dc1917fe477e263caa616594b8a886214957e0 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Mon, 12 Aug 2019 14:13:02 -0700 Subject: [PATCH 25/57] use established future posts pattern --- database.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/database.go b/database.go index d3ff338..1eea6bb 100644 --- a/database.go +++ b/database.go @@ -1535,13 +1535,11 @@ func (db *datastore) GetLastPinnedPostPos(collID int64) int64 { func (db *datastore) GetPinnedPosts(coll *CollectionObj, includeFuture bool) (*[]PublicPost, error) { // FIXME: sqlite-backed instances don't include ellipsis on truncated titles - rows := &sql.Rows{} - var err error - if includeFuture { - rows, err = db.Query("SELECT id, slug, title, "+db.clip("content", 80)+", pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL ORDER BY pinned_position ASC", coll.ID) - } else { - rows, err = db.Query("SELECT id, slug, title, "+db.clip("content", 80)+", pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL AND created <= "+db.now()+" ORDER BY pinned_position ASC", coll.ID) + timeCondition := "" + if !includeFuture { + timeCondition = "AND created <= " + db.now() } + rows, err := db.Query("SELECT id, slug, title, "+db.clip("content", 80)+", pinned_position FROM posts WHERE collection_id = ? AND pinned_position IS NOT NULL "+timeCondition+" ORDER BY pinned_position ASC", coll.ID) if err != nil { log.Error("Failed selecting pinned posts: %v", err) return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve pinned posts."} From 8a29a4dfc94b91a753391c4752b1ea4116d97ef6 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Wed, 14 Aug 2019 23:14:34 -0400 Subject: [PATCH 26/57] Link to home page in bare editor in chorus mode Ref T681 --- templates/bare.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/bare.tmpl b/templates/bare.tmpl index 1398a47..a4194c9 100644 --- a/templates/bare.tmpl +++ b/templates/bare.tmpl @@ -19,7 +19,7 @@
    - {{if not .SingleUser}}

    {{.SiteName}}

    {{end}} + {{if not .SingleUser}}

    {{if .Chorus}}{{else}}{{end}}{{.SiteName}}

    {{end}} From 55808233fd322373452f0c41240b542470251e6c Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Wed, 14 Aug 2019 23:25:02 -0400 Subject: [PATCH 27/57] Fix logic for showing sign up link This prevents the link from showing when an instance lands on the sign up page anyway. Ref T681 --- templates/base.tmpl | 2 +- templates/user/include/header.tmpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/base.tmpl b/templates/base.tmpl index f8b66c8..aae7850 100644 --- a/templates/base.tmpl +++ b/templates/base.tmpl @@ -47,7 +47,7 @@ {{if not .DisableDrafts}}Drafts{{end}} {{ end }} {{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}Reader{{end}} - {{if and (and .Chorus .OpenRegistration) (not .Username)}}Sign up{{end}} + {{if and (and (and .Chorus .OpenRegistration) (not .Username)) (or (not .Private) (ne .Landing ""))}}Sign up{{end}} {{if not .Username}}Log in{{else if .SimpleNav}}Log out{{end}} {{ end }} diff --git a/templates/user/include/header.tmpl b/templates/user/include/header.tmpl index 9d5bb54..0feca89 100644 --- a/templates/user/include/header.tmpl +++ b/templates/user/include/header.tmpl @@ -56,7 +56,7 @@ {{if not .DisableDrafts}}Drafts{{end}} {{ end }} {{if and (and .LocalTimeline .CanViewReader) (not .Chorus)}}Reader{{end}} - {{if and (and .Chorus .OpenRegistration) (not .Username)}}Sign up{{end}} + {{if and (and (and .Chorus .OpenRegistration) (not .Username)) (or (not .Private) (ne .Landing ""))}}Sign up{{end}} {{if .Username}}Log out{{else}}Log in{{end}} {{ end }} {{else}} From 954e57897bd335fba16864aa8ed24f16faeaaddb Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Sep 2019 17:40:02 -0400 Subject: [PATCH 28/57] Fix unpinning on chorus post page --- templates/chorus-collection-post.tmpl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/chorus-collection-post.tmpl b/templates/chorus-collection-post.tmpl index 392ebe6..bab2e31 100644 --- a/templates/chorus-collection-post.tmpl +++ b/templates/chorus-collection-post.tmpl @@ -100,14 +100,14 @@ function unpinPost(e, postID) { } pinning = true; - var $header = document.getElementsByTagName('header')[0]; + var $footer = document.getElementsByTagName('footer')[0]; var callback = function() { // Hide current page - var $pinnedNavLink = $header.getElementsByTagName('nav')[0].querySelector('.pinned.selected'); + var $pinnedNavLink = $footer.getElementsByTagName('nav')[0].querySelector('.pinned.selected'); $pinnedNavLink.style.display = 'none'; }; - var $pinBtn = $header.getElementsByClassName('unpin')[0]; + var $pinBtn = $footer.getElementsByClassName('unpin')[0]; $pinBtn.innerHTML = '...'; var http = new XMLHttpRequest(); From 8ec25f1fb49ad720569716d3afb1b21581850470 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Sep 2019 17:42:23 -0400 Subject: [PATCH 29/57] Fix pinning on chorus collection page Previously, the new pinned post link would appear in the site header, instead of the blog header. --- templates/chorus-collection.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/chorus-collection.tmpl b/templates/chorus-collection.tmpl index 06695b1..e36d3b5 100644 --- a/templates/chorus-collection.tmpl +++ b/templates/chorus-collection.tmpl @@ -174,7 +174,7 @@ function pinPost(e, postID, slug, title) { // Visibly remove post from collection var $postEl = document.getElementById('post-' + postID); $postEl.parentNode.removeChild($postEl); - var $header = document.getElementsByTagName('header')[0]; + var $header = document.querySelector('header:not(.multiuser)'); var $pinnedNavs = $header.getElementsByTagName('nav'); // Add link to nav var link = ''+title+''; From 4419632f83da051c48f98d74760d24658d065942 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 3 Sep 2019 17:56:27 -0400 Subject: [PATCH 30/57] Fix false login state on failed login Previously, a failed login would change the site-wide navigation so that it looked like the user was logged in, even though they weren't. This fixes that. --- account.go | 8 ++++---- pages/login.tmpl | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/account.go b/account.go index 357bae5..a3eb39d 100644 --- a/account.go +++ b/account.go @@ -309,10 +309,10 @@ func viewLogin(app *App, w http.ResponseWriter, r *http.Request) error { p := &struct { page.StaticPage - To string - Message template.HTML - Flashes []template.HTML - Username string + To string + Message template.HTML + Flashes []template.HTML + LoginUsername string }{ pageForReq(app, r), r.FormValue("to"), diff --git a/pages/login.tmpl b/pages/login.tmpl index 9b58523..1c8e862 100644 --- a/pages/login.tmpl +++ b/pages/login.tmpl @@ -12,8 +12,8 @@ {{end}} -
    -
    +
    +
    {{if .To}}{{end}} From 6396749f314bee72f351525696a9afd3411d6690 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 6 Sep 2019 19:49:15 -0700 Subject: [PATCH 31/57] default pad tempate on all pad renders this fixes a bug where if the `editor` config is set to an unsupported value there is a nil pointer error and the pad fails to render when editing only, not on a new post. --- pad.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/pad.go b/pad.go index 1545b4f..3857e38 100644 --- a/pad.go +++ b/pad.go @@ -11,12 +11,13 @@ package writefreely import ( + "net/http" + "strings" + "github.com/gorilla/mux" "github.com/writeas/impart" "github.com/writeas/web-core/log" "github.com/writeas/writefreely/page" - "net/http" - "strings" ) func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error { @@ -54,16 +55,13 @@ func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error { } padTmpl := app.cfg.App.Editor - if padTmpl == "" { + if templates[padTmpl] == nil { + log.Info("No template '%s' found. Falling back to default 'pad' template.", padTmpl) padTmpl = "pad" } if action == "" && slug == "" { // Not editing any post; simply render the Pad - if templates[padTmpl] == nil { - log.Info("No template '%s' found. Falling back to default 'pad' template.", padTmpl) - padTmpl = "pad" - } if err = templates[padTmpl].ExecuteTemplate(w, "pad", appData); err != nil { log.Error("Unable to execute template: %v", err) } From 151e996387660245c8b4fc0524921c1590c7c4d1 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 10 Sep 2019 21:21:45 +0200 Subject: [PATCH 32/57] Use new isOwner var in tests With the var there now, this makes the code a bit more readable. --- collections.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/collections.go b/collections.go index adf89d4..bf52862 100644 --- a/collections.go +++ b/collections.go @@ -770,7 +770,7 @@ func handleViewCollection(app *App, w http.ResponseWriter, r *http.Request) erro } } isOwner := owner != nil - if owner == nil { + if !isOwner { // Current user doesn't own collection; retrieve owner information owner, err = app.db.GetUserByID(coll.OwnerID) if err != nil { @@ -868,7 +868,7 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e } } isOwner := owner != nil - if owner == nil { + if !isOwner { // Current user doesn't own collection; retrieve owner information owner, err = app.db.GetUserByID(coll.OwnerID) if err != nil { From c7a90d2ace4adb33f8e8eb405545000cb02bccf0 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Tue, 10 Sep 2019 22:07:14 +0200 Subject: [PATCH 33/57] Fix blog post links when `chorus` enabled This ensures the "new post" link under each blog on the user Blogs page goes to /new instead of /. Ref T681 --- templates/user/collections.tmpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/user/collections.tmpl b/templates/user/collections.tmpl index 6ce4b75..481fd8f 100644 --- a/templates/user/collections.tmpl +++ b/templates/user/collections.tmpl @@ -13,7 +13,7 @@ {{if .Title}}{{.Title}}{{else}}{{.Alias}}{{end}}

    - new post + new post customize stats

    From feba200916f5b87889dd9ffdb5f9a384120860f8 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Wed, 11 Sep 2019 12:50:04 -0700 Subject: [PATCH 34/57] remove gogs/gogs/pkg/tool dependency this borrows some code from github.com/gogs/gogs/pkg/tool to avoid pulling it in as a dependency, along with many other indirect deps. --- admin.go | 41 +++++++++--------- go.mod | 20 --------- go.sum | 42 ------------------- stats.go | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 83 deletions(-) create mode 100644 stats.go diff --git a/admin.go b/admin.go index f316e3e..0aee818 100644 --- a/admin.go +++ b/admin.go @@ -18,7 +18,6 @@ import ( "strconv" "time" - "github.com/gogits/gogs/pkg/tool" "github.com/gorilla/mux" "github.com/writeas/impart" "github.com/writeas/web-core/auth" @@ -408,37 +407,37 @@ func handleAdminUpdateConfig(apper Apper, u *User, w http.ResponseWriter, r *htt } func updateAppStats() { - sysStatus.Uptime = tool.TimeSincePro(appStartTime) + sysStatus.Uptime = timeSincePro(appStartTime) m := new(runtime.MemStats) runtime.ReadMemStats(m) sysStatus.NumGoroutine = runtime.NumGoroutine() - sysStatus.MemAllocated = tool.FileSize(int64(m.Alloc)) - sysStatus.MemTotal = tool.FileSize(int64(m.TotalAlloc)) - sysStatus.MemSys = tool.FileSize(int64(m.Sys)) + sysStatus.MemAllocated = fileSize(int64(m.Alloc)) + sysStatus.MemTotal = fileSize(int64(m.TotalAlloc)) + sysStatus.MemSys = fileSize(int64(m.Sys)) sysStatus.Lookups = m.Lookups sysStatus.MemMallocs = m.Mallocs sysStatus.MemFrees = m.Frees - sysStatus.HeapAlloc = tool.FileSize(int64(m.HeapAlloc)) - sysStatus.HeapSys = tool.FileSize(int64(m.HeapSys)) - sysStatus.HeapIdle = tool.FileSize(int64(m.HeapIdle)) - sysStatus.HeapInuse = tool.FileSize(int64(m.HeapInuse)) - sysStatus.HeapReleased = tool.FileSize(int64(m.HeapReleased)) + sysStatus.HeapAlloc = fileSize(int64(m.HeapAlloc)) + sysStatus.HeapSys = fileSize(int64(m.HeapSys)) + sysStatus.HeapIdle = fileSize(int64(m.HeapIdle)) + sysStatus.HeapInuse = fileSize(int64(m.HeapInuse)) + sysStatus.HeapReleased = fileSize(int64(m.HeapReleased)) sysStatus.HeapObjects = m.HeapObjects - sysStatus.StackInuse = tool.FileSize(int64(m.StackInuse)) - sysStatus.StackSys = tool.FileSize(int64(m.StackSys)) - sysStatus.MSpanInuse = tool.FileSize(int64(m.MSpanInuse)) - sysStatus.MSpanSys = tool.FileSize(int64(m.MSpanSys)) - sysStatus.MCacheInuse = tool.FileSize(int64(m.MCacheInuse)) - sysStatus.MCacheSys = tool.FileSize(int64(m.MCacheSys)) - sysStatus.BuckHashSys = tool.FileSize(int64(m.BuckHashSys)) - sysStatus.GCSys = tool.FileSize(int64(m.GCSys)) - sysStatus.OtherSys = tool.FileSize(int64(m.OtherSys)) - - sysStatus.NextGC = tool.FileSize(int64(m.NextGC)) + sysStatus.StackInuse = fileSize(int64(m.StackInuse)) + sysStatus.StackSys = fileSize(int64(m.StackSys)) + sysStatus.MSpanInuse = fileSize(int64(m.MSpanInuse)) + sysStatus.MSpanSys = fileSize(int64(m.MSpanSys)) + sysStatus.MCacheInuse = fileSize(int64(m.MCacheInuse)) + sysStatus.MCacheSys = fileSize(int64(m.MCacheSys)) + sysStatus.BuckHashSys = fileSize(int64(m.BuckHashSys)) + sysStatus.GCSys = fileSize(int64(m.GCSys)) + sysStatus.OtherSys = fileSize(int64(m.OtherSys)) + + sysStatus.NextGC = fileSize(int64(m.NextGC)) sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) diff --git a/go.mod b/go.mod index 5e040ba..9c67aeb 100644 --- a/go.mod +++ b/go.mod @@ -2,25 +2,14 @@ module github.com/writeas/writefreely require ( github.com/BurntSushi/toml v0.3.1 // indirect - github.com/Unknwon/com v0.0.0-20181010210213-41959bdd855f // indirect - github.com/Unknwon/i18n v0.0.0-20171114194641-b64d33658966 // indirect github.com/alecthomas/gometalinter v3.0.0+incompatible // indirect github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect - github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 // indirect github.com/captncraig/cors v0.0.0-20180620154129-376d45073b49 // indirect github.com/clbanning/mxj v1.8.4 // indirect github.com/dustin/go-humanize v1.0.0 github.com/fatih/color v1.7.0 - github.com/go-macaron/cache v0.0.0-20151013081102-561735312776 // indirect - github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 // indirect - github.com/go-macaron/session v0.0.0-20190131233854-0a0a789bf193 // indirect github.com/go-sql-driver/mysql v1.4.1 github.com/go-test/deep v1.0.1 // indirect - github.com/gogits/gogs v0.11.86 - github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 // indirect - github.com/gogs/go-libravatar v0.0.0-20161120025154-cd1abbd55d09 // indirect - github.com/gogs/gogs v0.11.86 // indirect - github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a // indirect github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1 // indirect github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect github.com/gorilla/feeds v1.1.0 @@ -28,17 +17,13 @@ require ( github.com/gorilla/schema v1.0.2 github.com/gorilla/sessions v1.1.3 github.com/guregu/null v3.4.0+incompatible - github.com/ikeikeikeike/go-sitemap-generator v1.0.1 github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 - github.com/imdario/mergo v0.3.7 // indirect - github.com/jteeuwen/go-bindata v3.0.7+incompatible // indirect github.com/jtolds/gls v4.2.1+incompatible // indirect github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/manifoldco/promptui v0.3.2 github.com/mattn/go-colorable v0.1.0 // indirect github.com/mattn/go-sqlite3 v1.10.0 - github.com/mcuadros/go-version v0.0.0-20180611085657-6d5863ca60fa // indirect github.com/microcosm-cc/bluemonday v1.0.2 github.com/mitchellh/go-wordwrap v1.0.0 github.com/nicksnyder/go-i18n v1.10.0 // indirect @@ -46,7 +31,6 @@ require ( github.com/pelletier/go-toml v1.2.0 // indirect github.com/pkg/errors v0.8.1 // indirect github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect - github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect @@ -70,11 +54,7 @@ require ( golang.org/x/tools v0.0.0-20190208222737-3744606dbb67 // indirect google.golang.org/appengine v1.4.0 // indirect gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c // indirect - gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e // indirect - gopkg.in/clog.v1 v1.2.0 // indirect gopkg.in/ini.v1 v1.41.0 - gopkg.in/macaron.v1 v1.3.2 // indirect - gopkg.in/redis.v2 v2.3.2 // indirect gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect gopkg.in/yaml.v2 v2.2.2 // indirect ) diff --git a/go.sum b/go.sum index 8898bec..ec1e19d 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Unknwon/com v0.0.0-20181010210213-41959bdd855f h1:m1tYqjD/N0vF/S8s/ZKz/eccUr8RAAcrOK2MhXeTegA= -github.com/Unknwon/com v0.0.0-20181010210213-41959bdd855f/go.mod h1:KYCjqMOeHpNuTOiFQU6WEcTG7poCJrUs0YgyHNtn1no= -github.com/Unknwon/i18n v0.0.0-20171114194641-b64d33658966 h1:Mp8GNJ/tdTZIEdLdZfykEJaL3mTyEYrSzYNcdoQKpJk= -github.com/Unknwon/i18n v0.0.0-20171114194641-b64d33658966/go.mod h1:SFtfq0zFPsENI7DpE87QM2hcYu5QQ0fRdCgP+P1Hrqo= github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= github.com/alecthomas/gometalinter v3.0.0+incompatible h1:e9Zfvfytsw/e6Kd/PYd75wggK+/kX5Xn8IYDUKyc5fU= github.com/alecthomas/gometalinter v3.0.0+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= @@ -11,8 +7,6 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZq github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= -github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g= -github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= github.com/captncraig/cors v0.0.0-20180620154129-376d45073b49 h1:jWNY1NDg6a/c8RSXkai7IX6UOhir0LD39I4Dukg+4Ks= github.com/captncraig/cors v0.0.0-20180620154129-376d45073b49/go.mod h1:EIlIeMufZ8nqdUhnesledB15xLRl4wIJUppwDLPrdrQ= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= @@ -36,26 +30,10 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/go-fed/httpsig v0.1.0/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= -github.com/go-macaron/cache v0.0.0-20151013081102-561735312776 h1:UYIHS1r0WotqB5cIa0PAiV0m6GzD9rDBcn4alp5JgCw= -github.com/go-macaron/cache v0.0.0-20151013081102-561735312776/go.mod h1:hHAsZm/oBZVcY+S7qdQL6Vbg5VrXF6RuKGuqsszt3Ok= -github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI= -github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw= -github.com/go-macaron/session v0.0.0-20190131233854-0a0a789bf193 h1:z/nqwd+ql/r6Q3QGnwNd6B89UjPytM0be5pDQV9TuWw= -github.com/go-macaron/session v0.0.0-20190131233854-0a0a789bf193/go.mod h1:ScEJm9Gk+ez5JJTml5WlBIqavAfuE5nF8e4Gvyz/X+A= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= -github.com/gogits/gogs v0.11.86 h1:IujCpA+F/mYDXTcqdy593rl2donWakAWoL2HYZn7spw= -github.com/gogits/gogs v0.11.86/go.mod h1:H8FMbPPb+o/TgI6YnmQmT8nmEIHypXDau+f2CChYoCk= -github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561 h1:aBzukfDxQlCTVS0NBUjI5YA3iVeaZ9Tb5PxNrrIP1xs= -github.com/gogs/chardet v0.0.0-20150115103509-2404f7772561/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14= -github.com/gogs/go-libravatar v0.0.0-20161120025154-cd1abbd55d09 h1:UdOSIHZpkYcajRbfebBYzFDsL3SuqObH3bvKYBqgKmI= -github.com/gogs/go-libravatar v0.0.0-20161120025154-cd1abbd55d09/go.mod h1:Zas3BtO88pk1cwUfEYlvnl/CRwh0ybDxRWSwRjG8I3w= -github.com/gogs/gogs v0.11.86 h1:D+dXuY/6XjJ2t74W/dxo7ogx5+xW05Va8sJiQSS4WXA= -github.com/gogs/gogs v0.11.86/go.mod h1:qlbvdn16XTC6q7eR+thjW+OLdN+mi2PBZ8KqVT39T88= -github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a h1:8DZwxETOVWIinYxDK+i6L+rMb7eGATGaakD6ZucfHVk= -github.com/gogs/minwinsvc v0.0.0-20170301035411-95be6356811a/go.mod h1:TUIZ+29jodWQ8Gk6Pvtg4E09aMsc3C/VLZiVYfUhWQU= github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1 h1:6DVPu65tee05kY0/rciBQ47ue+AnuY8KTayV6VHikIo= github.com/golang/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= @@ -80,14 +58,8 @@ github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9R github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM= github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= -github.com/ikeikeikeike/go-sitemap-generator v1.0.1 h1:49Fn8gro/B12vCY8pf5/+/Jpr3kwB9TvP0MSymo69SY= -github.com/ikeikeikeike/go-sitemap-generator v1.0.1/go.mod h1:QI+zWsz6yQyxkG9LWNcnu0f7aiAE5tPdsZOsICgmd1c= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6ejs3Ut+jmsYvuOoAW2pSM= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw= -github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= -github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/jteeuwen/go-bindata v3.0.7+incompatible h1:91Uy4d9SYVr1kyTJ15wJsog+esAZZl7JmEfTkwmhJts= -github.com/jteeuwen/go-bindata v3.0.7+incompatible/go.mod h1:JVvhzYOiGBnFSYRyV00iY8q7/0PThjIYav1p9h5dmKs= github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= @@ -113,8 +85,6 @@ github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mcuadros/go-version v0.0.0-20180611085657-6d5863ca60fa h1:XvNrttGMJfVrUqblGju4IkjYXwx6l5OAAyjaIsydzsk= -github.com/mcuadros/go-version v0.0.0-20180611085657-6d5863ca60fa/go.mod h1:76rfSfYPWj01Z85hUf/ituArm797mNKcvINh1OlsZKo= github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= @@ -131,8 +101,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= -github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI= -github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY= @@ -167,8 +135,6 @@ github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g= github.com/writeas/slug v1.2.0/go.mod h1:RE8shOqQP3YhsfsQe0L3RnuejfQ4Mk+JjY5YJQFubfQ= github.com/writeas/web-core v1.0.0 h1:5VKkCakQgdKZcbfVKJXtRpc5VHrkflusCl/KRCPzpQ0= github.com/writeas/web-core v1.0.0/go.mod h1:Si3chV7VWgY8CsV+3gRolMXSO2Vx1ZFAQ/mkrpvmyEE= -github.com/writefreely/go-nodeinfo v1.1.0 h1:dp/ieEu0/gTeNKFvJTYhzBBouyFn7aiWtWzkb8J1JLg= -github.com/writefreely/go-nodeinfo v1.1.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg= github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss= github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg= golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59 h1:hk3yo72LXLapY9EXVttc3Z1rLOxT9IuAPPX3GpY2+jo= @@ -195,19 +161,11 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c h1:vTxShRUnK60yd8DZU+f95p1zSLj814+5CuEh7NjF2/Y= gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20180810215634-df19058c872c/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA= -gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e h1:wGA78yza6bu/mWcc4QfBuIEHEtc06xdiU0X8sY36yUU= -gopkg.in/bufio.v1 v1.0.0-20140618132640-567b2bfa514e/go.mod h1:xsQCaysVCudhrYTfzYWe577fCe7Ceci+6qjO2Rdc0Z4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/clog.v1 v1.2.0 h1:BHfwHRNQy497iBNsRBassPixSAxRbn2z5KVkdBFbwxc= -gopkg.in/clog.v1 v1.2.0/go.mod h1:L6fgdpdhFgKX4eGuDvt+N6X2GwZE160NRrIHzvaF8ZM= gopkg.in/ini.v1 v1.41.0 h1:Ka3ViY6gNYSKiVy71zXBEqKplnV35ImDLVG+8uoIklE= gopkg.in/ini.v1 v1.41.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/macaron.v1 v1.3.2 h1:AvWIaPmwBUA87/OWzePkoxeaw6YJWDfBt1pDFPBnLf8= -gopkg.in/macaron.v1 v1.3.2/go.mod h1:PrsiawTWAGZs6wFbT5hlr7SQ2Ns9h7cUVtcUu4lQOVo= -gopkg.in/redis.v2 v2.3.2 h1:GPVIIB/JnL1wvfULefy3qXmPu1nfNu2d0yA09FHgwfs= -gopkg.in/redis.v2 v2.3.2/go.mod h1:4wl9PJ/CqzeHk3LVq1hNLHH8krm3+AXEgut4jVc++LU= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU= gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= diff --git a/stats.go b/stats.go new file mode 100644 index 0000000..72b99be --- /dev/null +++ b/stats.go @@ -0,0 +1,124 @@ +package writefreely + +import ( + "fmt" + "math" + "strings" + "time" +) + +// Borrowed from github.com/gogs/gogs/pkg/tool + +// Seconds-based time units +const ( + Minute = 60 + Hour = 60 * Minute + Day = 24 * Hour + Week = 7 * Day + Month = 30 * Day + Year = 12 * Month +) + +func computeTimeDiff(diff int64) (int64, string) { + diffStr := "" + switch { + case diff <= 0: + diff = 0 + diffStr = "now" + case diff < 2: + diff = 0 + diffStr = "1 second" + case diff < 1*Minute: + diffStr = fmt.Sprintf("%d seconds", diff) + diff = 0 + + case diff < 2*Minute: + diff -= 1 * Minute + diffStr = "1 minute" + case diff < 1*Hour: + diffStr = fmt.Sprintf("%d minutes", diff/Minute) + diff -= diff / Minute * Minute + + case diff < 2*Hour: + diff -= 1 * Hour + diffStr = "1 hour" + case diff < 1*Day: + diffStr = fmt.Sprintf("%d hours", diff/Hour) + diff -= diff / Hour * Hour + + case diff < 2*Day: + diff -= 1 * Day + diffStr = "1 day" + case diff < 1*Week: + diffStr = fmt.Sprintf("%d days", diff/Day) + diff -= diff / Day * Day + + case diff < 2*Week: + diff -= 1 * Week + diffStr = "1 week" + case diff < 1*Month: + diffStr = fmt.Sprintf("%d weeks", diff/Week) + diff -= diff / Week * Week + + case diff < 2*Month: + diff -= 1 * Month + diffStr = "1 month" + case diff < 1*Year: + diffStr = fmt.Sprintf("%d months", diff/Month) + diff -= diff / Month * Month + + case diff < 2*Year: + diff -= 1 * Year + diffStr = "1 year" + default: + diffStr = fmt.Sprintf("%d years", diff/Year) + diff = 0 + } + return diff, diffStr +} + +// timeSincePro calculates the time interval and generate full user-friendly string. +func timeSincePro(then time.Time) string { + now := time.Now() + diff := now.Unix() - then.Unix() + + if then.After(now) { + return "future" + } + + var timeStr, diffStr string + for { + if diff == 0 { + break + } + + diff, diffStr = computeTimeDiff(diff) + timeStr += ", " + diffStr + } + return strings.TrimPrefix(timeStr, ", ") +} + +func logn(n, b float64) float64 { + return math.Log(n) / math.Log(b) +} + +func humanateBytes(s uint64, base float64, sizes []string) string { + if s < 10 { + return fmt.Sprintf("%d B", s) + } + e := math.Floor(logn(float64(s), base)) + suffix := sizes[int(e)] + val := float64(s) / math.Pow(base, math.Floor(e)) + f := "%.0f" + if val < 10 { + f = "%.1f" + } + + return fmt.Sprintf(f+" %s", val, suffix) +} + +// fileSize calculates the file size and generate user-friendly string. +func fileSize(s int64) string { + sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} + return humanateBytes(uint64(s), 1024, sizes) +} From 66b0945b70d6227309e8f7ccabaed8a2aec94acd Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 13 Sep 2019 07:11:48 -0400 Subject: [PATCH 35/57] Add copyright header to copied Gogs code --- stats.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/stats.go b/stats.go index 72b99be..36ca0f0 100644 --- a/stats.go +++ b/stats.go @@ -1,3 +1,7 @@ +// Copyright 2014-2018 The Gogs Authors. All rights reserved. +// Use of this source code is governed by a MIT-style license that can be +// found in the LICENSE file of the Gogs project (github.com/gogs/gogs). + package writefreely import ( From 0286dcf214c161d0f30cabe6dabf93b821d64c0d Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 13 Sep 2019 08:22:38 -0700 Subject: [PATCH 36/57] move tool from gogs into appstats pkg --- admin.go | 41 ++++++++++++++++---------------- stats.go => appstats/appstats.go | 10 ++++---- 2 files changed, 26 insertions(+), 25 deletions(-) rename stats.go => appstats/appstats.go (91%) diff --git a/admin.go b/admin.go index 0aee818..fdbb82f 100644 --- a/admin.go +++ b/admin.go @@ -22,6 +22,7 @@ import ( "github.com/writeas/impart" "github.com/writeas/web-core/auth" "github.com/writeas/web-core/log" + "github.com/writeas/writefreely/appstats" "github.com/writeas/writefreely/config" ) @@ -407,37 +408,37 @@ func handleAdminUpdateConfig(apper Apper, u *User, w http.ResponseWriter, r *htt } func updateAppStats() { - sysStatus.Uptime = timeSincePro(appStartTime) + sysStatus.Uptime = appstats.TimeSincePro(appStartTime) m := new(runtime.MemStats) runtime.ReadMemStats(m) sysStatus.NumGoroutine = runtime.NumGoroutine() - sysStatus.MemAllocated = fileSize(int64(m.Alloc)) - sysStatus.MemTotal = fileSize(int64(m.TotalAlloc)) - sysStatus.MemSys = fileSize(int64(m.Sys)) + sysStatus.MemAllocated = appstats.FileSize(int64(m.Alloc)) + sysStatus.MemTotal = appstats.FileSize(int64(m.TotalAlloc)) + sysStatus.MemSys = appstats.FileSize(int64(m.Sys)) sysStatus.Lookups = m.Lookups sysStatus.MemMallocs = m.Mallocs sysStatus.MemFrees = m.Frees - sysStatus.HeapAlloc = fileSize(int64(m.HeapAlloc)) - sysStatus.HeapSys = fileSize(int64(m.HeapSys)) - sysStatus.HeapIdle = fileSize(int64(m.HeapIdle)) - sysStatus.HeapInuse = fileSize(int64(m.HeapInuse)) - sysStatus.HeapReleased = fileSize(int64(m.HeapReleased)) + sysStatus.HeapAlloc = appstats.FileSize(int64(m.HeapAlloc)) + sysStatus.HeapSys = appstats.FileSize(int64(m.HeapSys)) + sysStatus.HeapIdle = appstats.FileSize(int64(m.HeapIdle)) + sysStatus.HeapInuse = appstats.FileSize(int64(m.HeapInuse)) + sysStatus.HeapReleased = appstats.FileSize(int64(m.HeapReleased)) sysStatus.HeapObjects = m.HeapObjects - sysStatus.StackInuse = fileSize(int64(m.StackInuse)) - sysStatus.StackSys = fileSize(int64(m.StackSys)) - sysStatus.MSpanInuse = fileSize(int64(m.MSpanInuse)) - sysStatus.MSpanSys = fileSize(int64(m.MSpanSys)) - sysStatus.MCacheInuse = fileSize(int64(m.MCacheInuse)) - sysStatus.MCacheSys = fileSize(int64(m.MCacheSys)) - sysStatus.BuckHashSys = fileSize(int64(m.BuckHashSys)) - sysStatus.GCSys = fileSize(int64(m.GCSys)) - sysStatus.OtherSys = fileSize(int64(m.OtherSys)) - - sysStatus.NextGC = fileSize(int64(m.NextGC)) + sysStatus.StackInuse = appstats.FileSize(int64(m.StackInuse)) + sysStatus.StackSys = appstats.FileSize(int64(m.StackSys)) + sysStatus.MSpanInuse = appstats.FileSize(int64(m.MSpanInuse)) + sysStatus.MSpanSys = appstats.FileSize(int64(m.MSpanSys)) + sysStatus.MCacheInuse = appstats.FileSize(int64(m.MCacheInuse)) + sysStatus.MCacheSys = appstats.FileSize(int64(m.MCacheSys)) + sysStatus.BuckHashSys = appstats.FileSize(int64(m.BuckHashSys)) + sysStatus.GCSys = appstats.FileSize(int64(m.GCSys)) + sysStatus.OtherSys = appstats.FileSize(int64(m.OtherSys)) + + sysStatus.NextGC = appstats.FileSize(int64(m.NextGC)) sysStatus.LastGC = fmt.Sprintf("%.1fs", float64(time.Now().UnixNano()-int64(m.LastGC))/1000/1000/1000) sysStatus.PauseTotalNs = fmt.Sprintf("%.1fs", float64(m.PauseTotalNs)/1000/1000/1000) sysStatus.PauseNs = fmt.Sprintf("%.3fs", float64(m.PauseNs[(m.NumGC+255)%256])/1000/1000/1000) diff --git a/stats.go b/appstats/appstats.go similarity index 91% rename from stats.go rename to appstats/appstats.go index 36ca0f0..bf27c7b 100644 --- a/stats.go +++ b/appstats/appstats.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style license that can be // found in the LICENSE file of the Gogs project (github.com/gogs/gogs). -package writefreely +package appstats import ( "fmt" @@ -81,8 +81,8 @@ func computeTimeDiff(diff int64) (int64, string) { return diff, diffStr } -// timeSincePro calculates the time interval and generate full user-friendly string. -func timeSincePro(then time.Time) string { +// TimeSincePro calculates the time interval and generate full user-friendly string. +func TimeSincePro(then time.Time) string { now := time.Now() diff := now.Unix() - then.Unix() @@ -121,8 +121,8 @@ func humanateBytes(s uint64, base float64, sizes []string) string { return fmt.Sprintf(f+" %s", val, suffix) } -// fileSize calculates the file size and generate user-friendly string. -func fileSize(s int64) string { +// FileSize calculates the file size and generate user-friendly string. +func FileSize(s int64) string { sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} return humanateBytes(uint64(s), 1024, sizes) } From d954b7c8e3884fb032dc869ea93a3d18fc9daff6 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 13 Sep 2019 10:58:17 -0700 Subject: [PATCH 37/57] add user invite instructions this adds a new page with instructions for sharing user invites if a user clicks the link for one of their own invite codes they are directed to a page with clear instructions for it's use. if a user clicks another users link they are redirectec to their account settings witha flash telling them they do not need to register. --- database.go | 13 +++++++++++ invites.go | 29 +++++++++++++++++++++---- routes.go | 9 ++++---- templates/user/invite-instructions.tmpl | 25 +++++++++++++++++++++ 4 files changed, 68 insertions(+), 8 deletions(-) create mode 100644 templates/user/invite-instructions.tmpl diff --git a/database.go b/database.go index 730bb60..a3235b6 100644 --- a/database.go +++ b/database.go @@ -2257,6 +2257,19 @@ func (db *datastore) GetUserInvite(id string) (*Invite, error) { return &i, nil } +// IsUsersInvite returns true if the user with ID created the invite with code +// and an error other than sql no rows, if any. Will return false in the event +// of an error. +func (db *datastore) IsUsersInvite(code string, userID int64) (bool, error) { + var id string + err := db.QueryRow("SELECT id FROM userinvites WHERE id = ? AND owner_id = ?", code, userID).Scan(&id) + if err != nil && err != sql.ErrNoRows { + log.Error("Failed selecting invite: %v", err) + return false, err + } + return id != "", nil +} + func (db *datastore) GetUsersInvitedCount(id string) int64 { var count int64 err := db.QueryRow("SELECT COUNT(*) FROM usersinvited WHERE invite_id = ?", id).Scan(&count) diff --git a/invites.go b/invites.go index 561255f..1e58ac3 100644 --- a/invites.go +++ b/invites.go @@ -12,15 +12,16 @@ package writefreely import ( "database/sql" + "html/template" + "net/http" + "strconv" + "time" + "github.com/gorilla/mux" "github.com/writeas/impart" "github.com/writeas/nerds/store" "github.com/writeas/web-core/log" "github.com/writeas/writefreely/page" - "html/template" - "net/http" - "strconv" - "time" ) type Invite struct { @@ -109,6 +110,26 @@ func handleCreateUserInvite(app *App, u *User, w http.ResponseWriter, r *http.Re func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { inviteCode := mux.Vars(r)["code"] + if u := getUserSession(app, r); u != nil { + // check if invite belongs to another user + // error can be ignored as not important in this case + if ownInvite, _ := app.db.IsUsersInvite(inviteCode, u.ID); !ownInvite { + addSessionFlash(app, w, r, "No need for an invite, You are already registered.", nil) + // show homepage + return impart.HTTPError{http.StatusFound, "/me/settings"} + } + // show invite instructions + p := struct { + *UserPage + InviteID string + }{ + UserPage: NewUserPage(app, r, u, "Invite Instructions", nil), + InviteID: inviteCode, + } + showUserPage(w, "invite-instructions", p) + return nil + } + i, err := app.db.GetUserInvite(inviteCode) if err != nil { return err diff --git a/routes.go b/routes.go index 777492b..0113e93 100644 --- a/routes.go +++ b/routes.go @@ -11,13 +11,14 @@ package writefreely import ( + "net/http" + "path/filepath" + "strings" + "github.com/gorilla/mux" "github.com/writeas/go-webfinger" "github.com/writeas/web-core/log" "github.com/writefreely/go-nodeinfo" - "net/http" - "path/filepath" - "strings" ) // InitStaticRoutes adds routes for serving static files. @@ -151,7 +152,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router { // Handle special pages first write.HandleFunc("/login", handler.Web(viewLogin, UserLevelNoneRequired)) write.HandleFunc("/signup", handler.Web(handleViewLanding, UserLevelNoneRequired)) - write.HandleFunc("/invite/{code}", handler.Web(handleViewInvite, UserLevelNoneRequired)).Methods("GET") + write.HandleFunc("/invite/{code}", handler.Web(handleViewInvite, UserLevelOptional)).Methods("GET") // TODO: show a reader-specific 404 page if the function is disabled write.HandleFunc("/read", handler.Web(viewLocalTimeline, UserLevelReader)) RouteRead(handler, UserLevelReader, write.PathPrefix("/read").Subrouter()) diff --git a/templates/user/invite-instructions.tmpl b/templates/user/invite-instructions.tmpl new file mode 100644 index 0000000..8bd2a30 --- /dev/null +++ b/templates/user/invite-instructions.tmpl @@ -0,0 +1,25 @@ +{{define "invite-instructions"}} +{{template "header" .}} + +
    +

    Invite Instructions

    +

    This is a special link that you can send to anyone you want to join {{ .SiteName }}. Copy the link below and paste it into an email, instant message, or text message and send it to the person you want. When they navigate to the link, they'll be able to create an account.

    + +
    + +{{template "footer" .}} +{{end}} From a6c1f4ae410fd525cbca6944269e8e57c06d2e9b Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Wed, 18 Sep 2019 08:21:33 -0700 Subject: [PATCH 38/57] allow titles for abbreviation elements this allows abbreviation elements to keep their title attributes when containing special characters. --- postrender.go | 1 + 1 file changed, 1 insertion(+) diff --git a/postrender.go b/postrender.go index 9ea6a7e..83fb5ad 100644 --- a/postrender.go +++ b/postrender.go @@ -170,6 +170,7 @@ func getSanitizationPolicy() *bluemonday.Policy { policy.AllowAttrs("controls", "loop", "muted", "autoplay").OnElements("video") policy.AllowAttrs("controls", "loop", "muted", "autoplay", "preload").OnElements("audio") policy.AllowAttrs("target").OnElements("a") + policy.AllowAttrs("title").OnElements("abbr") policy.AllowAttrs("style", "class", "id").Globally() policy.AllowURLSchemes("http", "https", "mailto", "xmpp") return policy From f87371b594ce78fa191c841e0fbf821b4b0839ab Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Wed, 18 Sep 2019 12:39:53 -0700 Subject: [PATCH 39/57] update IsJSON to check for Accept header this changes the helper IsJSON to take a request instead of a string, allowing to check multiple headers. In this case both Content-Type and Accept. --- account.go | 16 ++++++++-------- collections.go | 6 +++--- handle.go | 2 +- posts.go | 4 ++-- request.go | 12 ++++++++---- unregisteredusers.go | 7 ++++--- 6 files changed, 26 insertions(+), 21 deletions(-) diff --git a/account.go b/account.go index 2a66ecf..920fc9d 100644 --- a/account.go +++ b/account.go @@ -85,7 +85,7 @@ func apiSignup(app *App, w http.ResponseWriter, r *http.Request) error { } func signup(app *App, w http.ResponseWriter, r *http.Request) (*AuthUser, error) { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) // Get params var ur userRegistration @@ -120,7 +120,7 @@ func signup(app *App, w http.ResponseWriter, r *http.Request) (*AuthUser, error) } func signupWithRegistration(app *App, signup userRegistration, w http.ResponseWriter, r *http.Request) (*AuthUser, error) { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) // Validate required params (alias) if signup.Alias == "" { @@ -377,7 +377,7 @@ func webLogin(app *App, w http.ResponseWriter, r *http.Request) error { var loginAttemptUsers = sync.Map{} func login(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) oneTimeToken := r.FormValue("with") verbose := r.FormValue("all") == "true" || r.FormValue("verbose") == "1" || r.FormValue("verbose") == "true" || (reqJSON && oneTimeToken != "") @@ -580,7 +580,7 @@ func viewExportOptions(app *App, u *User, w http.ResponseWriter, r *http.Request func viewExportPosts(app *App, w http.ResponseWriter, r *http.Request) ([]byte, string, error) { var filename string var u = &User{} - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) if reqJSON { // Use given Authorization header accessToken := r.Header.Get("Authorization") @@ -662,7 +662,7 @@ func viewExportFull(app *App, w http.ResponseWriter, r *http.Request) ([]byte, s } func viewMeAPI(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) uObj := struct { ID int64 `json:"id,omitempty"` Username string `json:"username,omitempty"` @@ -686,7 +686,7 @@ func viewMeAPI(app *App, w http.ResponseWriter, r *http.Request) error { } func viewMyPostsAPI(app *App, u *User, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) if !reqJSON { return ErrBadRequestedType } @@ -717,7 +717,7 @@ func viewMyPostsAPI(app *App, u *User, w http.ResponseWriter, r *http.Request) e } func viewMyCollectionsAPI(app *App, u *User, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) if !reqJSON { return ErrBadRequestedType } @@ -822,7 +822,7 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques } func updateSettings(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) var s userSettings var u *User diff --git a/collections.go b/collections.go index c095ecb..cdf3d5c 100644 --- a/collections.go +++ b/collections.go @@ -338,7 +338,7 @@ func (c *Collection) RenderMathJax() bool { } func newCollection(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) alias := r.FormValue("alias") title := r.FormValue("title") @@ -454,7 +454,7 @@ func fetchCollection(app *App, w http.ResponseWriter, r *http.Request) error { c.hostName = app.cfg.App.Host // Redirect users who aren't requesting JSON - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) if !reqJSON { return impart.HTTPError{http.StatusFound, c.CanonicalURL()} } @@ -919,7 +919,7 @@ func handleCollectionPostRedirect(app *App, w http.ResponseWriter, r *http.Reque } func existingCollection(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) vars := mux.Vars(r) collAlias := vars["alias"] isWeb := r.FormValue("web") == "1" diff --git a/handle.go b/handle.go index 99c23ae..7e410f5 100644 --- a/handle.go +++ b/handle.go @@ -772,7 +772,7 @@ func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) return } - if IsJSON(r.Header.Get("Content-Type")) { + if IsJSON(r) { impart.WriteError(w, impart.HTTPError{http.StatusInternalServerError, "This is an unhelpful error message for a miscellaneous internal error."}) return } diff --git a/posts.go b/posts.go index 88730f7..1f35eda 100644 --- a/posts.go +++ b/posts.go @@ -472,7 +472,7 @@ func handleViewPost(app *App, w http.ResponseWriter, r *http.Request) error { // /posts?collection={alias} // ? /collections/{alias}/posts func newPost(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) vars := mux.Vars(r) collAlias := vars["alias"] if collAlias == "" { @@ -598,7 +598,7 @@ func newPost(app *App, w http.ResponseWriter, r *http.Request) error { } func existingPost(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) vars := mux.Vars(r) postID := vars["post"] diff --git a/request.go b/request.go index 4939f9c..2eb29f5 100644 --- a/request.go +++ b/request.go @@ -10,9 +10,13 @@ package writefreely -import "mime" +import ( + "mime" + "net/http" +) -func IsJSON(h string) bool { - ct, _, _ := mime.ParseMediaType(h) - return ct == "application/json" +func IsJSON(r *http.Request) bool { + ct, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type")) + accept := r.Header.Get("Accept") + return ct == "application/json" || accept == "application/json" } diff --git a/unregisteredusers.go b/unregisteredusers.go index 550c83b..b6f6ce6 100644 --- a/unregisteredusers.go +++ b/unregisteredusers.go @@ -13,13 +13,14 @@ package writefreely import ( "database/sql" "encoding/json" + "net/http" + "github.com/writeas/impart" "github.com/writeas/web-core/log" - "net/http" ) func handleWebSignup(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) // Get params var ur userRegistration @@ -71,7 +72,7 @@ func handleWebSignup(app *App, w http.ResponseWriter, r *http.Request) error { // { "username": "asdf" } // result: { code: 204 } func handleUsernameCheck(app *App, w http.ResponseWriter, r *http.Request) error { - reqJSON := IsJSON(r.Header.Get("Content-Type")) + reqJSON := IsJSON(r) // Get params var d struct { From 0066fecc20ef0bd120ab732e70b4f9791328424a Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Wed, 18 Sep 2019 17:06:40 -0400 Subject: [PATCH 40/57] Fix LESSC assignment in less/Makefile --- less/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/less/Makefile b/less/Makefile index 5e51b4d..85a64f3 100644 --- a/less/Makefile +++ b/less/Makefile @@ -1,4 +1,4 @@ -LESSC := $(shell command -v lessc 2> /dev/null) +LESSC=$(shell command -v lessc 2> /dev/null) ifndef LESSC $(error "less is not installed, please run install-less.sh") From d129894ba76e7b5a4850b9e3f98e39b4e8cd3242 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Wed, 18 Sep 2019 15:56:22 -0700 Subject: [PATCH 41/57] fix check for missing less --- less/Makefile | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/less/Makefile b/less/Makefile index 85a64f3..3484e29 100644 --- a/less/Makefile +++ b/less/Makefile @@ -1,15 +1,13 @@ -LESSC=$(shell command -v lessc 2> /dev/null) - -ifndef LESSC - $(error "less is not installed, please run install-less.sh") +ifeq (,$(shell command -v lessc 2> /dev/null)) +$(error "less is not installed, please run install-less.sh") endif CSSDIR=../static/css/ all : - $(LESSC) app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css - $(LESSC) fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css - $(LESSC) icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css + lessc app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css + lessc fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css + lessc icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css install : ./install-less.sh From 9d0027ec53506d35ede4d20b993e0ab198d66eea Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 20 Sep 2019 09:17:47 -0700 Subject: [PATCH 42/57] don't need less to install less --- less/Makefile | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/less/Makefile b/less/Makefile index 3484e29..3d0c4b2 100644 --- a/less/Makefile +++ b/less/Makefile @@ -1,12 +1,9 @@ -ifeq (,$(shell command -v lessc 2> /dev/null)) -$(error "less is not installed, please run install-less.sh") -endif - CSSDIR=../static/css/ all : - lessc app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css - lessc fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css + ifeq (,$(shell command -v lessc 2> /dev/null)) + $(error "less is not installed, please run install-less.sh") + endif lessc icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css install : From 43849d95d301bab16a15b92ba6eb658d1c9c1ea8 Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 20 Sep 2019 10:06:49 -0700 Subject: [PATCH 43/57] add back all generation steps accidentally removed two lines from make all fix check when trying to install lessc --- less/Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/less/Makefile b/less/Makefile index 3d0c4b2..da6be71 100644 --- a/less/Makefile +++ b/less/Makefile @@ -1,9 +1,11 @@ CSSDIR=../static/css/ all : - ifeq (,$(shell command -v lessc 2> /dev/null)) - $(error "less is not installed, please run install-less.sh") - endif +ifeq (,$(shell command -v lessc 2> /dev/null)) +$(error "less is not installed, please run install-less.sh") +endif + lessc app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css + lessc fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css lessc icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css install : From cb78fd227ecb2deba835cb423a6153e89c842cca Mon Sep 17 00:00:00 2001 From: Rob Loranger Date: Fri, 20 Sep 2019 10:17:58 -0700 Subject: [PATCH 44/57] use inline bash instead --- less/Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/less/Makefile b/less/Makefile index da6be71..1278c0a 100644 --- a/less/Makefile +++ b/less/Makefile @@ -1,9 +1,7 @@ CSSDIR=../static/css/ all : -ifeq (,$(shell command -v lessc 2> /dev/null)) -$(error "less is not installed, please run install-less.sh") -endif + @command -v lessc >/dev/null 2>&1 || { echo >&2 "less is not installed, please run install-less.sh."; exit 1; } lessc app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css lessc fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css lessc icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css From a01e2808906fb77db8516fcb393f9d8f009704b8 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Fri, 20 Sep 2019 18:22:01 -0400 Subject: [PATCH 45/57] Tweak "LESS not installed" message --- less/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/less/Makefile b/less/Makefile index 1278c0a..117e9b2 100644 --- a/less/Makefile +++ b/less/Makefile @@ -1,7 +1,7 @@ CSSDIR=../static/css/ all : - @command -v lessc >/dev/null 2>&1 || { echo >&2 "less is not installed, please run install-less.sh."; exit 1; } + @command -v lessc >/dev/null 2>&1 || { echo >&2 "lessc is not installed, please run: make install or: less/install-less.sh"; exit 1; } lessc app.less --clean-css="--s1 --advanced" $(CSSDIR)write.css lessc fonts.less --clean-css="--s1 --advanced" $(CSSDIR)fonts.css lessc icons.less --clean-css="--s1 --advanced" $(CSSDIR)icons.css From 891b15b8a8e077e27a19b9384d0bc4470b7bb5f6 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 23 Sep 2019 09:19:21 -0400 Subject: [PATCH 46/57] Always return invite errors This ensures we see a 404 page when looking up an invalid invite URL, even if the user is logged in. Ref T690 --- invites.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/invites.go b/invites.go index 1e58ac3..1bd75e6 100644 --- a/invites.go +++ b/invites.go @@ -110,6 +110,11 @@ func handleCreateUserInvite(app *App, u *User, w http.ResponseWriter, r *http.Re func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { inviteCode := mux.Vars(r)["code"] + i, err := app.db.GetUserInvite(inviteCode) + if err != nil { + return err + } + if u := getUserSession(app, r); u != nil { // check if invite belongs to another user // error can be ignored as not important in this case @@ -130,11 +135,6 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { return nil } - i, err := app.db.GetUserInvite(inviteCode) - if err != nil { - return err - } - p := struct { page.StaticPage Error string From 7e9e3cb7eb728d5da35da6bd9089a8b2c6bfd0b9 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 23 Sep 2019 09:45:36 -0400 Subject: [PATCH 47/57] Show status on logged-in expired invite links Ref T690 --- invites.go | 18 +++++++++++------- templates/user/invite-instructions.tmpl | 20 ++++++++++++-------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/invites.go b/invites.go index 1bd75e6..f7dcd7a 100644 --- a/invites.go +++ b/invites.go @@ -115,6 +115,13 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { return err } + expired := i.Expired() + if !expired && i.MaxUses.Valid && i.MaxUses.Int64 > 0 { + // Invite has a max-use number, so check if we're past that limit + i.uses = app.db.GetUsersInvitedCount(inviteCode) + expired = i.uses >= i.MaxUses.Int64 + } + if u := getUserSession(app, r); u != nil { // check if invite belongs to another user // error can be ignored as not important in this case @@ -123,13 +130,16 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { // show homepage return impart.HTTPError{http.StatusFound, "/me/settings"} } + // show invite instructions p := struct { *UserPage InviteID string + Expired bool }{ UserPage: NewUserPage(app, r, u, "Invite Instructions", nil), InviteID: inviteCode, + Expired: expired, } showUserPage(w, "invite-instructions", p) return nil @@ -145,16 +155,10 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { Invite: inviteCode, } - if i.Expired() { + if expired { p.Error = "This invite link has expired." } - if i.MaxUses.Valid && i.MaxUses.Int64 > 0 { - if c := app.db.GetUsersInvitedCount(inviteCode); c >= i.MaxUses.Int64 { - p.Error = "This invite link has expired." - } - } - // Get error messages session, err := app.sessionStore.Get(r, cookieName) if err != nil { diff --git a/templates/user/invite-instructions.tmpl b/templates/user/invite-instructions.tmpl index 8bd2a30..a70acd8 100644 --- a/templates/user/invite-instructions.tmpl +++ b/templates/user/invite-instructions.tmpl @@ -11,14 +11,18 @@

    Invite Instructions

    -

    This is a special link that you can send to anyone you want to join {{ .SiteName }}. Copy the link below and paste it into an email, instant message, or text message and send it to the person you want. When they navigate to the link, they'll be able to create an account.

    - + {{ if .Expired }} +

    This invite link is expired.

    + {{ else }} +

    This is a special link that you can send to anyone you want to join {{ .SiteName }}. Copy the link below and paste it into an email, instant message, or text message and send it to the person you want. When they navigate to the link, they'll be able to create an account.

    + + {{ end }}
    {{template "footer" .}} From f01b439ff531b88230640b78ae6bd7e1a1210d36 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 23 Sep 2019 10:02:36 -0400 Subject: [PATCH 48/57] Tweak invite page title and intro Ref T690 --- invites.go | 2 +- templates/user/invite-instructions.tmpl | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/invites.go b/invites.go index f7dcd7a..f9c5cce 100644 --- a/invites.go +++ b/invites.go @@ -137,7 +137,7 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { InviteID string Expired bool }{ - UserPage: NewUserPage(app, r, u, "Invite Instructions", nil), + UserPage: NewUserPage(app, r, u, "Invite to "+app.cfg.App.SiteName, nil), InviteID: inviteCode, Expired: expired, } diff --git a/templates/user/invite-instructions.tmpl b/templates/user/invite-instructions.tmpl index a70acd8..c99680a 100644 --- a/templates/user/invite-instructions.tmpl +++ b/templates/user/invite-instructions.tmpl @@ -10,18 +10,12 @@ }
    -

    Invite Instructions

    +

    Invite to {{.SiteName}}

    {{ if .Expired }}

    This invite link is expired.

    {{ else }} -

    This is a special link that you can send to anyone you want to join {{ .SiteName }}. Copy the link below and paste it into an email, instant message, or text message and send it to the person you want. When they navigate to the link, they'll be able to create an account.

    - +

    Copy the link below and send it to anyone that you want to join {{ .SiteName }}. You could paste it into an email, instant message, text message, or write it down on paper. Anyone who navigates to this special page will be able to create an account.

    + {{ end }}
    From 26a4f48e8b3b33fbfd79cc207052364fd9f5e08d Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 23 Sep 2019 10:04:45 -0400 Subject: [PATCH 49/57] Add expiration information to invite help This uses the Invite fetched from the database to explain a bit more about how the invite URL expires. It also reduces some space around the input box. Ref T690 --- invites.go | 4 ++-- templates/user/invite-instructions.tmpl | 13 +++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/invites.go b/invites.go index f9c5cce..2320906 100644 --- a/invites.go +++ b/invites.go @@ -134,11 +134,11 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { // show invite instructions p := struct { *UserPage - InviteID string + Invite *Invite Expired bool }{ UserPage: NewUserPage(app, r, u, "Invite to "+app.cfg.App.SiteName, nil), - InviteID: inviteCode, + Invite: i, Expired: expired, } showUserPage(w, "invite-instructions", p) diff --git a/templates/user/invite-instructions.tmpl b/templates/user/invite-instructions.tmpl index c99680a..a32eac0 100644 --- a/templates/user/invite-instructions.tmpl +++ b/templates/user/invite-instructions.tmpl @@ -3,7 +3,7 @@ From c6564b3d168fdaa7813aa9ce442f35fd8a89d562 Mon Sep 17 00:00:00 2001 From: Matt Baer Date: Mon, 23 Sep 2019 10:31:38 -0400 Subject: [PATCH 51/57] Shorten invite-instructions.tmpl filename --- invites.go | 2 +- templates/user/{invite-instructions.tmpl => invite-help.tmpl} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename templates/user/{invite-instructions.tmpl => invite-help.tmpl} (97%) diff --git a/invites.go b/invites.go index 2320906..3e19d7e 100644 --- a/invites.go +++ b/invites.go @@ -141,7 +141,7 @@ func handleViewInvite(app *App, w http.ResponseWriter, r *http.Request) error { Invite: i, Expired: expired, } - showUserPage(w, "invite-instructions", p) + showUserPage(w, "invite-help", p) return nil } diff --git a/templates/user/invite-instructions.tmpl b/templates/user/invite-help.tmpl similarity index 97% rename from templates/user/invite-instructions.tmpl rename to templates/user/invite-help.tmpl index e31e778..978cfad 100644 --- a/templates/user/invite-instructions.tmpl +++ b/templates/user/invite-help.tmpl @@ -1,4 +1,4 @@ -{{define "invite-instructions"}} +{{define "invite-help"}} {{template "header" .}}