Merge branch 'develop' into lang-posts-filter

pull/508/head
Matt Baer 1 year ago
commit 3dc515c249
  1. 8
      .github/dependabot.yml
  2. 61
      .github/workflows/docker-publish.yml
  3. 1
      .gitignore
  4. 11
      Dockerfile
  5. 65
      Makefile
  6. 2
      README.md
  7. 5
      SECURITY.md
  8. 19
      account.go
  9. 2
      activitypub.go
  10. 2
      admin.go
  11. 81
      app.go
  12. 2
      auth.go
  13. 12
      author/author.go
  14. 106
      bindata-lib.go
  15. 2
      cache.go
  16. 2
      cmd/writefreely/config.go
  17. 2
      cmd/writefreely/db.go
  18. 2
      cmd/writefreely/keys.go
  19. 2
      cmd/writefreely/main.go
  20. 2
      cmd/writefreely/user.go
  21. 2
      cmd/writefreely/web.go
  22. 76
      collections.go
  23. 4
      config/config.go
  24. 2
      config/data.go
  25. 2
      config/funcs.go
  26. 2
      config/setup.go
  27. 2
      config/validation.go
  28. 3
      database-lib.go
  29. 3
      database-no-sqlite.go
  30. 3
      database-sqlite.go
  31. 64
      database.go
  32. 2
      db/create.go
  33. 2
      errors.go
  34. 2
      export.go
  35. 16
      feed.go
  36. 82
      go.mod
  37. 155
      go.sum
  38. 4
      gopher.go
  39. 14
      handle.go
  40. 2
      hostmeta.go
  41. 2
      instance.go
  42. 5
      invites.go
  43. 2
      key/key.go
  44. 2
      keys.go
  45. 1
      less/core.less
  46. 2
      less/login.less
  47. 2
      migrations/drivers.go
  48. 3
      migrations/migrations.go
  49. 2
      migrations/v1.go
  50. 2
      migrations/v10.go
  51. 38
      migrations/v11.go
  52. 2
      migrations/v2.go
  53. 2
      migrations/v3.go
  54. 2
      migrations/v4.go
  55. 2
      migrations/v5.go
  56. 2
      migrations/v6.go
  57. 2
      migrations/v7.go
  58. 2
      migrations/v8.go
  59. 2
      migrations/v9.go
  60. 2
      monetization.go
  61. 2
      nodeinfo.go
  62. 12
      oauth.go
  63. 2
      oauth_generic.go
  64. 31
      oauth_gitea.go
  65. 2
      oauth_signup.go
  66. 2
      oauth_slack.go
  67. 2
      oauth_test.go
  68. 10
      ossl_legacy.cnf
  69. 5
      pad.go
  70. 3
      page/page.go
  71. 2
      pages.go
  72. 3
      pages/404-general.tmpl
  73. 2
      parse/posts.go
  74. 2
      parse/posts_test.go
  75. 23
      postrender.go
  76. 2
      postrender_test.go
  77. 4
      posts.go
  78. 2
      posts_test.go
  79. 4
      prose/markdownSerializer.js
  80. 22
      prose/package-lock.json
  81. 8
      prose/prose.js
  82. 2
      read.go
  83. 2
      request.go
  84. 3
      routes.go
  85. 2
      scripts/invalidate-css.sh
  86. 2
      scripts/upgrade-server.sh
  87. 11
      session.go
  88. 2
      sitemap.go
  89. 2
      static/js/modals.js
  90. 2
      static/js/webmonetization.js
  91. 18
      templates.go
  92. 1
      templates/bare.tmpl
  93. 7
      templates/base.tmpl
  94. 1
      templates/chorus-collection-post.tmpl
  95. 1
      templates/chorus-collection.tmpl
  96. 1
      templates/classic.tmpl
  97. 1
      templates/collection-post.tmpl
  98. 12
      templates/collection-tags.tmpl
  99. 11
      templates/collection.tmpl
  100. 3
      templates/edit-meta.tmpl
  101. Some files were not shown because too many files have changed in this diff Show More

@ -5,3 +5,11 @@ updates:
open-pull-requests-limit: 50 open-pull-requests-limit: 50
schedule: schedule:
interval: "monthly" interval: "monthly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "docker"
directory: "/Dockerfile"
schedule:
interval: "daily"

@ -0,0 +1,61 @@
name: Build container image, publish as Github-package
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
push:
branches: [ main, develop ]
# Publish semver tags as releases.
tags:
- 'v*.*.*'
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@v3.0.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v4.6.0
with:
images: |
ghcr.io/${{ github.repository }}
flavor: latest=true
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker images
uses: docker/build-push-action@v5.0.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

1
.gitignore vendored

@ -3,6 +3,7 @@ node_modules
*.swp *.swp
*.swo *.swo
static/local/custom.css
build build
tmp tmp
*.ini *.ini

@ -1,16 +1,21 @@
# Build image # Build image
FROM golang:1.15-alpine as build FROM golang:1.19-alpine as build
LABEL org.opencontainers.image.source=https://github.com/writefreely/writefreely
LABEL org.opencontainers.image.description="WriteFreely is a clean, minimalist publishing platform made for writers. Start a blog, share knowledge within your organization, or build a community around the shared act of writing."
RUN apk add --update nodejs npm make g++ git RUN apk add --update nodejs npm make g++ git
RUN npm install -g less less-plugin-clean-css RUN npm install -g less less-plugin-clean-css
RUN go get -u github.com/go-bindata/go-bindata/...
RUN mkdir -p /go/src/github.com/writefreely/writefreely RUN mkdir -p /go/src/github.com/writefreely/writefreely
WORKDIR /go/src/github.com/writefreely/writefreely WORKDIR /go/src/github.com/writefreely/writefreely
COPY . . COPY . .
RUN cat ossl_legacy.cnf > /etc/ssl/openssl.cnf
ENV GO111MODULE=on ENV GO111MODULE=on
ENV NODE_OPTIONS=--openssl-legacy-provider
RUN make build \ RUN make build \
&& make ui && make ui
@ -24,7 +29,7 @@ RUN mkdir /stage && \
/stage /stage
# Final image # Final image
FROM alpine:3.12 FROM alpine:3
RUN apk add --no-cache openssl ca-certificates RUN apk add --no-cache openssl ca-certificates
COPY --from=build --chown=daemon:daemon /stage /go COPY --from=build --chown=daemon:daemon /stage /go

@ -1,5 +1,5 @@
GITREV=`git describe | cut -c 2-` GITREV=`git describe | cut -c 2-`
LDFLAGS=-ldflags="-X 'github.com/writefreely/writefreely.softwareVer=$(GITREV)'" LDFLAGS=-ldflags="-s -w -X 'github.com/writefreely/writefreely.softwareVer=$(GITREV)'"
GOCMD=go GOCMD=go
GOINSTALL=$(GOCMD) install $(LDFLAGS) GOINSTALL=$(GOCMD) install $(LDFLAGS)
@ -14,50 +14,50 @@ TMPBIN=./tmp
all : build all : build
ci: ci-assets deps ci: deps
cd cmd/writefreely; $(GOBUILD) -v cd cmd/writefreely; $(GOBUILD) -v
build: assets deps build: deps
cd cmd/writefreely; $(GOBUILD) -v -tags='sqlite' cd cmd/writefreely; $(GOBUILD) -v -tags='netgo sqlite'
build-no-sqlite: assets-no-sqlite deps-no-sqlite build-no-sqlite: deps-no-sqlite
cd cmd/writefreely; $(GOBUILD) -v -o $(BINARY_NAME) cd cmd/writefreely; $(GOBUILD) -v -tags='netgo' -o $(BINARY_NAME)
build-linux: deps build-linux: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi fi
xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely xgo --targets=linux/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-windows: deps build-windows: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi fi
xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely xgo --targets=windows/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-darwin: deps build-darwin: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi fi
xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely xgo --targets=darwin/amd64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-arm6: deps build-arm6: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi fi
xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely xgo --targets=linux/arm-6, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-arm7: deps build-arm7: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi fi
xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely xgo --targets=linux/arm-7, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-arm64: deps build-arm64: deps
@hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ @hash xgo > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u src.techknowlogick.com/xgo; \ $(GOCMD) install src.techknowlogick.com/xgo@latest; \
fi fi
xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='sqlite' -go go-1.15.x -out writefreely ./cmd/writefreely xgo --targets=linux/arm64, -dest build/ $(LDFLAGS) -tags='netgo sqlite' -go go-1.19.x -out writefreely -pkg ./cmd/writefreely .
build-docker : build-docker :
$(DOCKERCMD) build -t $(IMAGE_NAME):latest -t $(IMAGE_NAME):$(GITREV) . $(DOCKERCMD) build -t $(IMAGE_NAME):latest -t $(IMAGE_NAME):$(GITREV) .
@ -65,8 +65,8 @@ build-docker :
test: test:
$(GOTEST) -v ./... $(GOTEST) -v ./...
run: dev-assets run:
$(GOINSTALL) -tags='sqlite' ./... $(GOINSTALL) -tags='netgo sqlite' ./...
$(BINARY_NAME) --debug $(BINARY_NAME) --debug
deps : deps :
@ -81,7 +81,7 @@ install : build
cmd/writefreely/$(BINARY_NAME) --init-db cmd/writefreely/$(BINARY_NAME) --init-db
cd less/; $(MAKE) install $(MFLAGS) cd less/; $(MAKE) install $(MFLAGS)
release : clean ui assets release : clean ui
mkdir -p $(BUILDPATH) mkdir -p $(BUILDPATH)
cp -r templates $(BUILDPATH) cp -r templates $(BUILDPATH)
cp -r pages $(BUILDPATH) cp -r pages $(BUILDPATH)
@ -133,35 +133,12 @@ ui : force_look
cd less/; $(MAKE) $(MFLAGS) cd less/; $(MAKE) $(MFLAGS)
cd prose/; $(MAKE) $(MFLAGS) cd prose/; $(MAKE) $(MFLAGS)
assets : generate
go-bindata -pkg writefreely -ignore=\\.gitignore -tags="!wflib" schema.sql sqlite.sql
assets-no-sqlite: generate
go-bindata -pkg writefreely -ignore=\\.gitignore -tags="!wflib" schema.sql
dev-assets : generate
go-bindata -pkg writefreely -ignore=\\.gitignore -debug -tags="!wflib" schema.sql sqlite.sql
lib-assets : generate
go-bindata -pkg writefreely -ignore=\\.gitignore -o bindata-lib.go -tags="wflib" schema.sql
generate :
@hash go-bindata > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GOGET) -u github.com/jteeuwen/go-bindata/go-bindata; \
fi
$(TMPBIN): $(TMPBIN):
mkdir -p $(TMPBIN) mkdir -p $(TMPBIN)
$(TMPBIN)/go-bindata: deps $(TMPBIN)
$(GOBUILD) -o $(TMPBIN)/go-bindata github.com/jteeuwen/go-bindata/go-bindata
$(TMPBIN)/xgo: deps $(TMPBIN) $(TMPBIN)/xgo: deps $(TMPBIN)
$(GOBUILD) -o $(TMPBIN)/xgo src.techknowlogick.com/xgo $(GOBUILD) -o $(TMPBIN)/xgo src.techknowlogick.com/xgo
ci-assets : $(TMPBIN)/go-bindata
$(TMPBIN)/go-bindata -pkg writefreely -ignore=\\.gitignore -tags="!wflib" schema.sql sqlite.sql
clean : clean :
-rm -rf build -rm -rf build
-rm -rf tmp -rm -rf tmp

@ -86,4 +86,4 @@ Before contributing anything, please read our [Contributing Guide](https://githu
## License ## License
Copyright © 2018-2021 [A Bunch Tell LLC](https://abunchtell.com) and contributing authors. Licensed under the [AGPL](https://github.com/writefreely/writefreely/blob/develop/LICENSE). Copyright © 2018-2022 [Musing Studio LLC](https://musing.studio) and contributing authors. Licensed under the [AGPL](https://github.com/writefreely/writefreely/blob/develop/LICENSE).

@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
To report a vulnerability, send an email to security@writefreely.org.

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -787,6 +787,9 @@ func viewArticles(app *App, u *User, w http.ResponseWriter, r *http.Request) err
silenced, err := app.db.IsUserSilenced(u.ID) silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil { if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view articles: %v", err) log.Error("view articles: %v", err)
} }
d := struct { d := struct {
@ -822,7 +825,10 @@ func viewCollections(app *App, u *User, w http.ResponseWriter, r *http.Request)
silenced, err := app.db.IsUserSilenced(u.ID) silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil { if err != nil {
log.Error("view collections %v", err) if err == ErrUserNotFound {
return err
}
log.Error("view collections: %v", err)
return fmt.Errorf("view collections: %v", err) return fmt.Errorf("view collections: %v", err)
} }
d := struct { d := struct {
@ -861,6 +867,9 @@ func viewEditCollection(app *App, u *User, w http.ResponseWriter, r *http.Reques
silenced, err := app.db.IsUserSilenced(u.ID) silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil { if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view edit collection %v", err) log.Error("view edit collection %v", err)
return fmt.Errorf("view edit collection: %v", err) return fmt.Errorf("view edit collection: %v", err)
} }
@ -1038,6 +1047,9 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error
silenced, err := app.db.IsUserSilenced(u.ID) silenced, err := app.db.IsUserSilenced(u.ID)
if err != nil { if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view stats: %v", err) log.Error("view stats: %v", err)
return err return err
} }
@ -1071,6 +1083,9 @@ func viewStats(app *App, u *User, w http.ResponseWriter, r *http.Request) error
func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) error { func viewSettings(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
fullUser, err := app.db.GetUserByID(u.ID) fullUser, err := app.db.GetUserByID(u.ID)
if err != nil { if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("Unable to get user for settings: %s", err) log.Error("Unable to get user for settings: %s", err)
return impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data. The humans have been alerted."} return impart.HTTPError{http.StatusInternalServerError, "Unable to retrieve user data. The humans have been alerted."}
} }

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -13,9 +13,11 @@ package writefreely
import ( import (
"crypto/tls" "crypto/tls"
"database/sql" "database/sql"
_ "embed"
"fmt" "fmt"
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
@ -35,12 +37,13 @@ import (
"github.com/writeas/web-core/auth" "github.com/writeas/web-core/auth"
"github.com/writeas/web-core/converter" "github.com/writeas/web-core/converter"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
"golang.org/x/crypto/acme/autocert"
"github.com/writefreely/writefreely/author" "github.com/writefreely/writefreely/author"
"github.com/writefreely/writefreely/config" "github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/key" "github.com/writefreely/writefreely/key"
"github.com/writefreely/writefreely/migrations" "github.com/writefreely/writefreely/migrations"
"github.com/writefreely/writefreely/page" "github.com/writefreely/writefreely/page"
"golang.org/x/crypto/acme/autocert"
) )
const ( const (
@ -56,7 +59,7 @@ var (
debugging bool debugging bool
// Software version can be set from git env using -ldflags // Software version can be set from git env using -ldflags
softwareVer = "0.13.1" softwareVer = "0.13.2"
// DEPRECATED VARS // DEPRECATED VARS
isSingleUser bool isSingleUser bool
@ -356,6 +359,11 @@ func pageForReq(app *App, r *http.Request) page.StaticPage {
Version: "v" + softwareVer, Version: "v" + softwareVer,
} }
// Use custom style, if file exists
if _, err := os.Stat(filepath.Join(staticDir, "local", "custom.css")); err == nil {
p.CustomCSS = true
}
// Add user information, if given // Add user information, if given
var u *User var u *User
accessToken := r.FormValue("t") accessToken := r.FormValue("t")
@ -503,9 +511,41 @@ requests. We recommend supplying a valid host name.`)
err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r) err = http.ListenAndServeTLS(fmt.Sprintf("%s:443", bindAddress), app.cfg.Server.TLSCertPath, app.cfg.Server.TLSKeyPath, r)
} }
} else { } else {
log.Info("Serving on http://%s:%d\n", bindAddress, app.cfg.Server.Port) network := "tcp"
protocol := "http"
if strings.HasPrefix(bindAddress, "/") {
network = "unix"
protocol = "http+unix"
// old sockets will remain after server closes;
// we need to delete them in order to open new ones
err = os.Remove(bindAddress)
if err != nil && !os.IsNotExist(err) {
log.Error("%s already exists but could not be removed: %v", bindAddress, err)
os.Exit(1)
}
} else {
bindAddress = fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port)
}
log.Info("Serving on %s://%s", protocol, bindAddress)
log.Info("---") log.Info("---")
err = http.ListenAndServe(fmt.Sprintf("%s:%d", bindAddress, app.cfg.Server.Port), r) listener, err := net.Listen(network, bindAddress)
if err != nil {
log.Error("Could not bind to address: %v", err)
os.Exit(1)
}
if network == "unix" {
err = os.Chmod(bindAddress, 0o666)
if err != nil {
log.Error("Could not update socket permissions: %v", err)
os.Exit(1)
}
}
defer listener.Close()
err = http.Serve(listener, r)
} }
if err != nil { if err != nil {
log.Error("Unable to start: %v", err) log.Error("Unable to start: %v", err)
@ -812,6 +852,16 @@ func connectToDatabase(app *App) {
func shutdown(app *App) { func shutdown(app *App) {
log.Info("Closing database connection...") log.Info("Closing database connection...")
app.db.Close() app.db.Close()
if strings.HasPrefix(app.cfg.Server.Bind, "/") {
// Clean up socket
log.Info("Removing socket file...")
err := os.Remove(app.cfg.Server.Bind)
if err != nil {
log.Error("Unable to remove socket: %s", err)
os.Exit(1)
}
log.Info("Success.")
}
} }
// CreateUser creates a new admin or normal user from the given credentials. // CreateUser creates a new admin or normal user from the given credentials.
@ -874,15 +924,18 @@ func CreateUser(apper Apper, username, password string, isAdmin bool) error {
return nil return nil
} }
//go:embed schema.sql
var schemaSql string
//go:embed sqlite.sql
var sqliteSql string
func adminInitDatabase(app *App) error { func adminInitDatabase(app *App) error {
schemaFileName := "schema.sql" var schema string
if app.cfg.Database.Type == driverSQLite { if app.cfg.Database.Type == driverSQLite {
schemaFileName = "sqlite.sql" schema = sqliteSql
} } else {
schema = schemaSql
schema, err := Asset(schemaFileName)
if err != nil {
return fmt.Errorf("Unable to load schema file: %v", err)
} }
tblReg := regexp.MustCompile("CREATE TABLE (IF NOT EXISTS )?`([a-z_]+)`") tblReg := regexp.MustCompile("CREATE TABLE (IF NOT EXISTS )?`([a-z_]+)`")
@ -898,7 +951,7 @@ func adminInitDatabase(app *App) error {
} else { } else {
log.Info("Creating table ??? (Weird query) No match in: %v", parts) log.Info("Creating table ??? (Weird query) No match in: %v", parts)
} }
_, err = app.db.Exec(q) _, err := app.db.Exec(q)
if err != nil { if err != nil {
log.Error("%s", err) log.Error("%s", err)
} else { } else {
@ -908,7 +961,7 @@ func adminInitDatabase(app *App) error {
// Set up migrations table // Set up migrations table
log.Info("Initializing appmigrations table...") log.Info("Initializing appmigrations table...")
err = migrations.SetInitialMigrations(migrations.NewDatastore(app.db.DB, app.db.driverName)) err := migrations.SetInitialMigrations(migrations.NewDatastore(app.db.DB, app.db.driverName))
if err != nil { if err != nil {
return fmt.Errorf("Unable to set initial migrations: %v", err) return fmt.Errorf("Unable to set initial migrations: %v", err)
} }

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -11,6 +11,7 @@
package author package author
import ( import (
"github.com/writeas/web-core/log"
"github.com/writefreely/writefreely/config" "github.com/writefreely/writefreely/config"
"os" "os"
"path/filepath" "path/filepath"
@ -113,10 +114,17 @@ func IsValidUsername(cfg *config.Config, username string) bool {
// Username is invalid if page with the same name exists. So traverse // Username is invalid if page with the same name exists. So traverse
// available pages, adding them to reservedUsernames map that'll be checked // available pages, adding them to reservedUsernames map that'll be checked
// later. // later.
filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, "pages"), func(path string, i os.FileInfo, err error) error { err := filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, "pages"), func(path string, i os.FileInfo, err error) error {
if err != nil {
return err
}
reservedUsernames[i.Name()] = true reservedUsernames[i.Name()] = true
return nil return nil
}) })
if err != nil {
log.Error("[IMPORTANT WARNING]: Could not determine IsValidUsername! %s", err)
return false
}
// Username is invalid if it is reserved! // Username is invalid if it is reserved!
if _, reserved := reservedUsernames[username]; reserved { if _, reserved := reservedUsernames[username]; reserved {

@ -1,106 +0,0 @@
// +build wflib
package writefreely
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"strings"
)
func bindata_read(data []byte, name string) ([]byte, error) {
gz, err := gzip.NewReader(bytes.NewBuffer(data))
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
var buf bytes.Buffer
_, err = io.Copy(&buf, gz)
gz.Close()
if err != nil {
return nil, fmt.Errorf("Read %q: %v", name, err)
}
return buf.Bytes(), nil
}
var _schema_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x59\x5f\x6f\xa3\x38\x10\x7f\xef\xa7\xf0\xdb\xa6\x52\x23\x6d\x7a\xdd\xaa\xba\xd3\x3e\x64\x53\x76\x2f\xba\x94\xee\x25\x44\xba\x7d\x02\x03\x93\xd4\xaa\xb1\x91\x6d\x92\xe6\xdb\x9f\x8c\x49\x08\x86\x24\xd0\xdb\x3b\x71\x7d\x2a\xcc\x6f\x8c\xfd\x9b\x3f\x9e\x99\x0c\x87\x57\xc3\x21\x7a\xc4\x0a\x87\x58\xc2\xaf\x28\xd8\x0a\xa2\x60\x25\x00\xe8\x2e\xb8\x1a\x0e\xaf\xb4\x78\xf8\xce\x3f\xad\xac\xf5\x3d\x1c\x52\x40\x52\x89\x2c\x52\x99\x00\xb4\xe2\x02\xa9\xfc\x5d\x80\xa3\x08\xa4\x54\xfc\x15\x98\x34\xdf\x9b\xcc\x9d\xb1\xe7\x20\x6f\xfc\x65\xe6\xa0\xe9\x57\xe4\x3e\x7b\xc8\xf9\x6b\xba\xf0\x16\x16\x1a\x0d\xae\x10\x0a\xf2\x87\x00\x85\x84\x61\xb1\x1b\x8c\xee\xaf\x73\x05\x77\x39\x9b\xdd\x68\x71\x26\x41\xf8\x24\x0e\x10\x61\x6a\x60\x0b\x65\x16\xf3\x00\x29\xc2\x76\x5a\x3a\x2a\xa5\xe8\xd1\xf9\x3a\x5e\xce\x3c\xf4\xe1\xe3\x87\x1c\xc9\x19\xf8\x8a\x24\xd0\x0e\x1d\x09\xc0\x0a\xe2\x00\xc5\x58\x81\x56\xab\x43\x27\xcb\xf9\xdc\x71\x3d\xdf\x9b\x3e\x39\x0b\x6f\xfc\xf4\x3d\x57\x84\xb7\x94\x08\x90\x47\x8a\x7b\x7c\xf5\x40\x78\x0d\x4c\x05\x68\x83\x45\xf4\x82\xc5\xe0\xf6\xd3\xa7\xeb\x1a\xf2\xfb\x7c\xfa\x34\x9e\xff\x40\x7f\x38\x3f\xd0\xa0\xa0\xe9\xfa\xea\x1a\x39\xee\xb7\xa9\xeb\x7c\x9e\x32\xc6\x1f\xbf\x94\xfb\xf9\x7d\x3c\x5f\x38\xde\x67\x8a\x15\x61\xa3\xdf\xfe\x75\xb3\xa7\x69\xc4\x99\xd2\xa7\xb8\x6c\xf4\x12\x6b\x4c\xae\xcd\xb9\x3f\xfa\x2f\xb6\x4d\x0f\xd0\x04\x62\x92\x25\x0a\xde\x54\x7e\xb8\xf1\xc4\x73\xe6\x68\xe1\x78\x28\x53\xab\x07\x34\x79\x9e\xcd\xf4\x17\xf5\x83\x1f\x12\x66\x79\x4d\x1a\xbf\xcb\x80\x55\xce\x49\xdc\x2b\xc2\x13\xb2\x16\x58\x11\xde\x18\x68\x16\xc0\x10\xbd\x01\x21\x09\x67\x26\x78\x46\x23\x8b\x69\x03\x6f\x64\x29\x97\x0b\x90\x19\x55\x01\xca\x4d\xb0\x97\xf4\x85\x8f\x88\x53\x0a\x91\x3e\x2c\x56\x4a\x90\x30\x53\xd0\x22\xff\x34\x6a\x19\xae\x4a\xd1\xc9\x74\x73\xd0\x29\xdd\x77\x74\xfb\x60\x81\x36\x98\x66\x60\x85\x76\xdd\x7f\x93\xf0\xae\xe2\xc2\x49\x78\x57\xf3\xe2\xaa\x33\x56\xf7\x77\x73\xb4\x99\xde\xf8\x68\xb9\xc5\x57\xd8\x75\xb2\x46\x8e\x6f\x6d\x87\x34\x0b\x29\x89\xfc\x57\xd8\x05\x28\xa4\x3c\xb4\xa4\x82\x6c\xb0\x82\x13\xe2\x73\xa4\xf6\x90\xc8\x14\x4b\xb9\xe5\x22\xee\xc4\x66\xa9\xd4\x9e\xd2\x42\x25\x40\xb9\xd7\xde\x7f\xbc\xfe\x3f\xb3\x26\x20\x26\x02\x22\xd5\x89\xb5\x52\xc9\xb0\x96\x0a\xd8\xf8\x98\x12\x2c\x8f\xc2\xfd\xa3\x45\x4c\xc0\x60\x7b\x11\x54\x65\xef\x68\xdd\x1e\x52\xd7\x89\x32\x79\x74\xa1\x5b\x5e\x85\xc6\x4b\xef\xd9\x9f\xba\x93\xb9\xf3\xe4\xb8\x9e\xc9\x9f\x0d\x3c\xb5\x4f\x8d\xb5\x4a\x4a\x11\x45\x7f\x4e\xa6\x0d\x62\x90\x91\x20\xa9\xca\x2f\xcb\xc3\xfe\xee\x3b\xed\xaf\x5a\x99\xaa\x1d\x05\x5f\xbe\x00\x14\x17\xa8\x79\x9b\x7f\xa4\xb8\x51\x5b\xaf\x9c\xab\xae\xb8\x48\xf0\x51\xc9\xf8\x50\x2f\x18\x4d\xe6\x8b\x76\x8d\x35\xae\xa9\x82\xb7\xec\x4c\x35\xbd\x21\xb0\xf5\x23\x9e\xe9\xe2\xab\x41\x5e\xaf\x8d\xf4\xdb\xa5\x3b\xfd\x73\xe9\xe4\x2f\xf7\xf6\x1d\x04\x3d\xf3\xee\x94\xcb\x36\xa9\xc0\xc0\x4a\x8f\x2e\x9c\xc0\xee\x39\x68\xb6\xb6\x7c\xb8\x66\x88\x84\xc7\x64\xb5\xf3\x8b\xd6\xc6\xd4\xb9\xb7\x0d\x38\xed\x07\x3e\x4e\x53\xc0\x02\xb3\x08\x0a\xe8\x5d\x53\x67\xc2\xb8\x48\x4c\x73\x42\x31\x5b\x67\x78\xbd\x47\x37\xad\x2b\x14\xad\x38\xc1\x4f\xf0\x94\xda\x12\xcd\x97\x4a\xfd\x4b\x84\x31\x88\xfd\x94\x4b\x62\xa2\xeb\xe8\x8b\x4b\x77\x31\xfd\xe6\x3a\x8f\x0d\x8b\xef\x1b\x30\x5d\x95\x4a\x85\x93\xb4\x6d\x07\x76\xa8\xfc\x3b\x6b\x5e\x70\x7f\x3b\xdd\xfc\x93\xec\x70\xe8\x71\xba\x25\x82\x8e\xe1\x48\x62\xdf\x38\x6b\xbd\x78\xcc\xdf\xd7\x14\x4a\xa3\x0f\xca\xff\x6f\x0e\x6b\xe7\x98\xc2\x73\x0a\xd4\xde\x8f\x6e\x7a\xd5\x2b\x09\x48\xb8\x82\x15\xa7\x94\x6f\x5b\xc4\x7d\x15\x7e\xb2\x64\xaa\xf5\x4f\x46\xcf\xaf\x4c\x28\x6a\xa0\xd3\xa3\x84\xcb\x25\xbe\xf5\x81\x9e\xf1\xab\xb7\xd5\xae\xce\xb7\xf0\xf5\x21\x40\x7e\x75\x77\xe7\xf6\x6c\x1f\x70\x39\x3e\x8c\xc5\x0f\x1e\xdf\x7f\xb6\x3b\x51\x6d\xd7\x66\xc7\xec\x35\x16\x67\x91\xe2\x86\x8a\xd3\x56\x21\x2c\xe4\x6f\xe7\x00\xf2\x05\x0b\x88\xfd\x4b\xb8\xcb\xb6\xb1\xe2\x6f\x50\x6e\xaf\x37\x76\xd1\x24\x77\x99\x3d\x58\x78\x63\x9d\xb3\xe3\xcd\x86\x79\xc3\xfd\xdd\x7f\x34\x6e\xd8\x6f\xac\x97\x83\x06\xbd\x39\xc2\x36\xa4\x99\xf7\x8a\xd8\x2a\xe7\x6c\x8a\xab\x75\x4e\x7d\x44\x86\xdf\x74\x42\x90\x01\x92\x09\xa6\xf4\x64\x2d\x74\x36\xc9\xb7\x99\x0a\x13\x86\x23\x45\x36\xcd\xf3\xe9\x3e\xd1\xde\xd2\xd1\x3b\x76\x86\x5a\x85\xe1\x04\xde\xdd\x1c\x5e\x1a\x66\x54\x57\x32\x7c\x1d\x16\x32\x8f\xf5\x75\x20\xc1\x84\xe6\x5b\x2a\x7e\x9d\x68\x9c\xd3\xbf\xfb\xd7\x82\xcb\x59\xb0\xa4\x65\x50\xfe\xdf\xab\x28\x94\x26\xce\xe2\x53\x61\x78\x90\x17\xee\x90\x3f\xf9\x27\xc3\xf1\xe4\x7d\xdf\xfa\xcc\x7f\x07\x00\x00\xff\xff\xbe\x79\x68\xa8\x10\x1b\x00\x00")
func schema_sql() ([]byte, error) {
return bindata_read(
_schema_sql,
"schema.sql",
)
}
// Asset loads and returns the asset for the given name.
// It returns an error if the asset could not be found or
// could not be loaded.
func Asset(name string) ([]byte, error) {
cannonicalName := strings.Replace(name, "\\", "/", -1)
if f, ok := _bindata[cannonicalName]; ok {
return f()
}
return nil, fmt.Errorf("Asset %s not found", name)
}
// AssetNames returns the names of the assets.
func AssetNames() []string {
names := make([]string, 0, len(_bindata))
for name := range _bindata {
names = append(names, name)
}
return names
}
// _bindata is a table, holding each asset generator, mapped to its name.
var _bindata = map[string]func() ([]byte, error){
"schema.sql": schema_sql,
}
// AssetDir returns the file names below a certain
// directory embedded in the file by go-bindata.
// For example if you run go-bindata on data/... and data contains the
// following hierarchy:
// data/
// foo.txt
// img/
// a.png
// b.png
// then AssetDir("data") would return []string{"foo.txt", "img"}
// AssetDir("data/img") would return []string{"a.png", "b.png"}
// AssetDir("foo.txt") and AssetDir("notexist") would return an error
// AssetDir("") will return []string{"data"}.
func AssetDir(name string) ([]string, error) {
node := _bintree
if len(name) != 0 {
cannonicalName := strings.Replace(name, "\\", "/", -1)
pathList := strings.Split(cannonicalName, "/")
for _, p := range pathList {
node = node.Children[p]
if node == nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
}
}
if node.Func != nil {
return nil, fmt.Errorf("Asset %s not found", name)
}
rv := make([]string, 0, len(node.Children))
for name := range node.Children {
rv = append(rv, name)
}
return rv, nil
}
type _bintree_t struct {
Func func() ([]byte, error)
Children map[string]*_bintree_t
}
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"schema.sql": &_bintree_t{schema_sql, map[string]*_bintree_t{}},
}}

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2022 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -24,13 +24,14 @@ import (
"unicode" "unicode"
"github.com/gorilla/mux" "github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/impart" "github.com/writeas/impart"
"github.com/writeas/web-core/activitystreams" "github.com/writeas/web-core/activitystreams"
"github.com/writeas/web-core/auth" "github.com/writeas/web-core/auth"
"github.com/writeas/web-core/bots" "github.com/writeas/web-core/bots"
"github.com/writeas/web-core/i18n" "github.com/writeas/web-core/i18n"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
waposts "github.com/writeas/web-core/posts" "github.com/writeas/web-core/posts"
"github.com/writefreely/writefreely/author" "github.com/writefreely/writefreely/author"
"github.com/writefreely/writefreely/config" "github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/page" "github.com/writefreely/writefreely/page"
@ -363,6 +364,26 @@ func (c *Collection) MonetizationURL() string {
return strings.Replace(c.Monetization, "$", "https://", 1) return strings.Replace(c.Monetization, "$", "https://", 1)
} }
// DisplayDescription returns the description with rendered Markdown and HTML.
func (c *Collection) DisplayDescription() *template.HTML {
if c.Description == "" {
s := template.HTML("")
return &s
}
t := template.HTML(posts.ApplyBasicAccessibleMarkdown([]byte(c.Description)))
return &t
}
// PlainDescription returns the description with all Markdown and HTML removed.
func (c *Collection) PlainDescription() string {
if c.Description == "" {
return ""
}
desc := stripHTMLWithoutEscaping(c.Description)
desc = stripmd.Strip(desc)
return desc
}
func (c CollectionPage) DisplayMonetization() string { func (c CollectionPage) DisplayMonetization() string {
return displayMonetization(c.Monetization, c.Alias) return displayMonetization(c.Monetization, c.Alias)
} }
@ -562,11 +583,11 @@ func fetchCollectionPosts(app *App, w http.ResponseWriter, r *http.Request) erro
} }
} }
posts, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false) ps, err := app.db.GetPosts(app.cfg, c, page, isCollOwner, false, false)
if err != nil { if err != nil {
return err return err
} }
coll := &CollectionObj{Collection: *c, Posts: posts} coll := &CollectionObj{Collection: *c, Posts: ps}
app.db.GetPostsCount(coll, isCollOwner) app.db.GetPostsCount(coll, isCollOwner)
// Strip non-public information // Strip non-public information
coll.Collection.ForPublic() coll.Collection.ForPublic()
@ -574,7 +595,7 @@ func fetchCollectionPosts(app *App, w http.ResponseWriter, r *http.Request) erro
// Transform post bodies if needed // Transform post bodies if needed
if r.FormValue("body") == "html" { if r.FormValue("body") == "html" {
for _, p := range *coll.Posts { for _, p := range *coll.Posts {
p.Content = waposts.ApplyMarkdown([]byte(p.Content)) p.Content = posts.ApplyMarkdown([]byte(p.Content))
} }
} }
@ -600,6 +621,30 @@ type CollectionPage struct {
CollAlias string CollAlias string
} }
type TagCollectionPage struct {
CollectionPage
Tag string
}
func (tcp TagCollectionPage) PrevPageURL(prefix string, n int, tl bool) string {
u := fmt.Sprintf("/tag:%s", tcp.Tag)
if n > 2 {
u += fmt.Sprintf("/page/%d", n-1)
}
if tl {
return u
}
return "/" + prefix + tcp.Alias + u
}
func (tcp TagCollectionPage) NextPageURL(prefix string, n int, tl bool) string {
if tl {
return fmt.Sprintf("/tag:%s/page/%d", tcp.Tag, n+1)
}
return fmt.Sprintf("/%s%s/tag:%s/page/%d", prefix, tcp.Alias, tcp.Tag, n+1)
}
func NewCollectionObj(c *Collection) *CollectionObj { func NewCollectionObj(c *Collection) *CollectionObj {
return &CollectionObj{ return &CollectionObj{
Collection: *c, Collection: *c,
@ -941,16 +986,29 @@ func handleViewCollectionTag(app *App, w http.ResponseWriter, r *http.Request) e
coll := newDisplayCollection(c, cr, page) coll := newDisplayCollection(c, cr, page)
taggedPostIDs, err := app.db.GetAllPostsTaggedIDs(c, tag, cr.isCollOwner)
if err != nil {
return err
}
ttlPosts := len(taggedPostIDs)
pagePosts := coll.Format.PostsPerPage()
coll.TotalPages = int(math.Ceil(float64(ttlPosts) / float64(pagePosts)))
if coll.TotalPages > 0 && page > coll.TotalPages {
redirURL := fmt.Sprintf("/page/%d", coll.TotalPages)
if !app.cfg.App.SingleUser {
redirURL = fmt.Sprintf("/%s%s%s", cr.prefix, coll.Alias, redirURL)
}
return impart.HTTPError{http.StatusFound, redirURL}
}
coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, page, cr.isCollOwner) coll.Posts, _ = app.db.GetPostsTagged(app.cfg, c, tag, page, cr.isCollOwner)
if coll.Posts != nil && len(*coll.Posts) == 0 { if coll.Posts != nil && len(*coll.Posts) == 0 {
return ErrCollectionPageNotFound return ErrCollectionPageNotFound
} }
// Serve collection // Serve collection
displayPage := struct { displayPage := TagCollectionPage{
CollectionPage
Tag string
}{
CollectionPage: CollectionPage{ CollectionPage: CollectionPage{
DisplayCollection: coll, DisplayCollection: coll,
StaticPage: pageForReq(app, r), StaticPage: pageForReq(app, r),

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -15,9 +15,9 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/go-ini/ini"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
"golang.org/x/net/idna" "golang.org/x/net/idna"
"gopkg.in/ini.v1"
) )
const ( const (

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018, 2020-2021 A Bunch Tell LLC. * Copyright © 2018, 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,7 +1,8 @@
//go:build wflib
// +build wflib // +build wflib
/* /*
* Copyright © 2019-2020 A Bunch Tell LLC. * Copyright © 2019-2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,7 +1,8 @@
//go:build !sqlite && !wflib
// +build !sqlite,!wflib // +build !sqlite,!wflib
/* /*
* Copyright © 2019-2020 A Bunch Tell LLC. * Copyright © 2019-2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,7 +1,8 @@
//go:build sqlite && !wflib
// +build sqlite,!wflib // +build sqlite,!wflib
/* /*
* Copyright © 2019-2020 A Bunch Tell LLC. * Copyright © 2019-2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -113,6 +113,7 @@ type writestore interface {
GetPostsCount(c *CollectionObj, includeFuture bool) GetPostsCount(c *CollectionObj, includeFuture bool)
GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error) GetPosts(cfg *config.Config, c *Collection, page int, includeFuture, forceRecentFirst, includePinned bool) (*[]PublicPost, error)
GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error)
GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error) GetPostsTagged(cfg *config.Config, c *Collection, tag string, page int, includeFuture bool) (*[]PublicPost, error)
GetAPFollowers(c *Collection) (*[]RemoteUser, error) GetAPFollowers(c *Collection) (*[]RemoteUser, error)
@ -332,7 +333,7 @@ func (db *datastore) IsUserSilenced(id int64) (bool, error) {
err := db.QueryRow("SELECT status FROM users WHERE id = ?", id).Scan(&u.Status) err := db.QueryRow("SELECT status FROM users WHERE id = ?", id).Scan(&u.Status)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
return false, fmt.Errorf("is user silenced: %v", ErrUserNotFound) return false, ErrUserNotFound
case err != nil: case err != nil:
log.Error("Couldn't SELECT user status: %v", err) log.Error("Couldn't SELECT user status: %v", err)
return false, fmt.Errorf("is user silenced: %v", err) return false, fmt.Errorf("is user silenced: %v", err)
@ -661,7 +662,7 @@ func (db *datastore) CreatePost(userID, collID int64, post *SubmittedPost) (*Pos
// SQLite stores datetimes in UTC, so convert time.Now() to it here // SQLite stores datetimes in UTC, so convert time.Now() to it here
created = created.UTC() created = created.UTC()
} }
if post.Created != nil { if post.Created != nil && *post.Created != "" {
created, err = time.Parse("2006-01-02T15:04:05Z", *post.Created) created, err = time.Parse("2006-01-02T15:04:05Z", *post.Created)
if err != nil { if err != nil {
log.Error("Unable to parse Created time '%s': %v", *post.Created, err) log.Error("Unable to parse Created time '%s': %v", *post.Created, err)
@ -924,7 +925,7 @@ func (db *datastore) UpdateCollection(c *SubmittedCollection, alias string) erro
} }
} }
if !skipUpdate { if !skipUpdate {
_, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = ?", collID, "monetization_pointer", *c.Monetization, *c.Monetization) _, err = db.Exec("INSERT INTO collectionattributes (collection_id, attribute, value) VALUES (?, ?, ?) "+db.upsert("collection_id", "attribute")+" value = ?", collID, "monetization_pointer", *c.Monetization, *c.Monetization)
if err != nil { if err != nil {
log.Error("Unable to insert monetization_pointer value: %v", err) log.Error("Unable to insert monetization_pointer value: %v", err)
return err return err
@ -1195,6 +1196,51 @@ func (db *datastore) GetPosts(cfg *config.Config, c *Collection, page int, inclu
return &posts, nil return &posts, nil
} }
func (db *datastore) GetAllPostsTaggedIDs(c *Collection, tag string, includeFuture bool) ([]string, error) {
collID := c.ID
cf := c.NewFormat()
order := "DESC"
if cf.Ascending() {
order = "ASC"
}
timeCondition := ""
if !includeFuture {
timeCondition = "AND created <= NOW()"
}
var rows *sql.Rows
var err error
if db.driverName == driverSQLite {
rows, err = db.Query("SELECT id FROM posts WHERE collection_id = ? AND LOWER(content) regexp ? "+timeCondition+" ORDER BY created "+order, collID, `.*#`+strings.ToLower(tag)+`\b.*`)
} else {
rows, err = db.Query("SELECT id FROM posts WHERE collection_id = ? AND LOWER(content) RLIKE ? "+timeCondition+" ORDER BY created "+order, collID, "#"+strings.ToLower(tag)+"[[:>:]]")
}
if err != nil {
log.Error("Failed selecting tagged posts: %v", err)
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve tagged collection posts."}
}
defer rows.Close()
ids := []string{}
for rows.Next() {
var id string
err = rows.Scan(&id)
if err != nil {
log.Error("Failed scanning row: %v", err)
break
}
ids = append(ids, id)
}
err = rows.Err()
if err != nil {
log.Error("Error after Next() on rows: %v", err)
}
return ids, nil
}
// GetPostsTagged retrieves all posts on the given Collection that contain the // GetPostsTagged retrieves all posts on the given Collection that contain the
// given tag. // given tag.
// It will return future posts if `includeFuture` is true. // It will return future posts if `includeFuture` is true.
@ -1756,7 +1802,7 @@ func (db *datastore) GetPublicCollections(hostName string) (*[]Collection, error
FROM collections c FROM collections c
LEFT JOIN users u ON u.id = c.owner_id LEFT JOIN users u ON u.id = c.owner_id
WHERE c.privacy = 1 AND u.status = 0 WHERE c.privacy = 1 AND u.status = 0
ORDER BY id ASC`) ORDER BY title ASC`)
if err != nil { if err != nil {
log.Error("Failed selecting public collections: %v", err) log.Error("Failed selecting public collections: %v", err)
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve public collections."} return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve public collections."}
@ -1842,7 +1888,7 @@ func (db *datastore) GetTopPosts(u *User, alias string, hostName string) (*[]Pub
where = " AND alias = ?" where = " AND alias = ?"
params = append(params, alias) params = append(params, alias)
} }
rows, err := db.Query("SELECT p.id, p.slug, p.view_count, p.title, c.alias, c.title, c.description, c.view_count FROM posts p LEFT JOIN collections c ON p.collection_id = c.id WHERE p.owner_id = ?"+where+" ORDER BY p.view_count DESC, created DESC LIMIT 25", params...) rows, err := db.Query("SELECT p.id, p.slug, p.view_count, p.title, p.content, c.alias, c.title, c.description, c.view_count FROM posts p LEFT JOIN collections c ON p.collection_id = c.id WHERE p.owner_id = ?"+where+" ORDER BY p.view_count DESC, created DESC LIMIT 25", params...)
if err != nil { if err != nil {
log.Error("Failed selecting from posts: %v", err) log.Error("Failed selecting from posts: %v", err)
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user top posts."} return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user top posts."}
@ -1856,7 +1902,7 @@ func (db *datastore) GetTopPosts(u *User, alias string, hostName string) (*[]Pub
c := Collection{} c := Collection{}
var alias, title, description sql.NullString var alias, title, description sql.NullString
var views sql.NullInt64 var views sql.NullInt64
err = rows.Scan(&p.ID, &p.Slug, &p.ViewCount, &p.Title, &alias, &title, &description, &views) err = rows.Scan(&p.ID, &p.Slug, &p.ViewCount, &p.Title, &p.Content, &alias, &title, &description, &views)
if err != nil { if err != nil {
log.Error("Failed scanning User.getPosts() row: %v", err) log.Error("Failed scanning User.getPosts() row: %v", err)
gotErr = true gotErr = true
@ -1901,7 +1947,7 @@ func (db *datastore) GetAnonymousPosts(u *User, page int) (*[]PublicPost, error)
if page > 0 { if page > 0 {
limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts) limitStr = fmt.Sprintf(" LIMIT %d, %d", start, pagePosts)
} }
rows, err := db.Query("SELECT id, view_count, title, created, updated, content FROM posts WHERE owner_id = ? AND collection_id IS NULL ORDER BY created DESC"+limitStr, u.ID) rows, err := db.Query("SELECT id, view_count, title, language, created, updated, content FROM posts WHERE owner_id = ? AND collection_id IS NULL ORDER BY created DESC"+limitStr, u.ID)
if err != nil { if err != nil {
log.Error("Failed selecting from posts: %v", err) log.Error("Failed selecting from posts: %v", err)
return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user anonymous posts."} return nil, impart.HTTPError{http.StatusInternalServerError, "Couldn't retrieve user anonymous posts."}
@ -1911,7 +1957,7 @@ func (db *datastore) GetAnonymousPosts(u *User, page int) (*[]PublicPost, error)
posts := []PublicPost{} posts := []PublicPost{}
for rows.Next() { for rows.Next() {
p := Post{} p := Post{}
err = rows.Scan(&p.ID, &p.ViewCount, &p.Title, &p.Created, &p.Updated, &p.Content) err = rows.Scan(&p.ID, &p.ViewCount, &p.Title, &p.Language, &p.Created, &p.Updated, &p.Content)
if err != nil { if err != nil {
log.Error("Failed scanning row: %v", err) log.Error("Failed scanning row: %v", err)
break break

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019-2020 A Bunch Tell LLC. * Copyright © 2019-2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2020 A Bunch Tell LLC. * Copyright © 2018-2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019 A Bunch Tell LLC. * Copyright © 2018-2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2020 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -15,7 +15,7 @@ import (
"net/http" "net/http"
"time" "time"
. "github.com/gorilla/feeds" "github.com/gorilla/feeds"
"github.com/gorilla/mux" "github.com/gorilla/mux"
stripmd "github.com/writeas/go-strip-markdown/v2" stripmd "github.com/writeas/go-strip-markdown/v2"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
@ -87,11 +87,11 @@ func ViewFeed(app *App, w http.ResponseWriter, req *http.Request) error {
siteURL += "tag:" + tag siteURL += "tag:" + tag
} }
feed := &Feed{ feed := &feeds.Feed{
Title: collectionTitle, Title: collectionTitle,
Link: &Link{Href: siteURL}, Link: &feeds.Link{Href: siteURL},
Description: coll.Description, Description: coll.Description,
Author: &Author{author, ""}, Author: &feeds.Author{author, ""},
Created: time.Now(), Created: time.Now(),
} }
@ -103,13 +103,13 @@ func ViewFeed(app *App, w http.ResponseWriter, req *http.Request) error {
// Create the item for the feed // Create the item for the feed
title = p.PlainDisplayTitle() title = p.PlainDisplayTitle()
permalink = fmt.Sprintf("%s%s", baseUrl, p.Slug.String) permalink = fmt.Sprintf("%s%s", baseUrl, p.Slug.String)
feed.Items = append(feed.Items, &Item{ feed.Items = append(feed.Items, &feeds.Item{
Id: fmt.Sprintf("%s%s", basePermalinkUrl, p.Slug.String), Id: fmt.Sprintf("%s%s", basePermalinkUrl, p.Slug.String),
Title: title, Title: title,
Link: &Link{Href: permalink}, Link: &feeds.Link{Href: permalink},
Description: "<![CDATA[" + stripmd.Strip(p.Content) + "]]>", Description: "<![CDATA[" + stripmd.Strip(p.Content) + "]]>",
Content: string(p.HTMLContent), Content: string(p.HTMLContent),
Author: &Author{author, ""}, Author: &feeds.Author{author, ""},
Created: p.Created, Created: p.Created,
Updated: p.Updated, Updated: p.Updated,
}) })

@ -1,34 +1,26 @@
module github.com/writefreely/writefreely module github.com/writefreely/writefreely
require ( require (
git.mills.io/prologic/go-gopher v0.0.0-20210712135410-b7ebb55feece github.com/dustin/go-humanize v1.0.1
github.com/clbanning/mxj v1.8.4 // indirect github.com/fatih/color v1.15.0
github.com/dustin/go-humanize v1.0.0 github.com/go-ini/ini v1.67.0
github.com/fatih/color v1.10.0 github.com/go-sql-driver/mysql v1.7.1
github.com/go-sql-driver/mysql v1.6.0 github.com/gorilla/csrf v1.7.1
github.com/go-test/deep v1.0.1 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gorilla/csrf v1.7.0
github.com/gorilla/feeds v1.1.1 github.com/gorilla/feeds v1.1.1
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/schema v1.2.0 github.com/gorilla/schema v1.2.0
github.com/gorilla/sessions v1.2.0 github.com/gorilla/sessions v1.2.1
github.com/guregu/null v3.5.0+incompatible github.com/guregu/null v4.0.0+incompatible
github.com/hashicorp/go-multierror v1.1.1 github.com/hashicorp/go-multierror v1.1.1
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec
github.com/lunixbochs/vtclean v1.0.0 // indirect github.com/manifoldco/promptui v0.9.0
github.com/manifoldco/promptui v0.8.0 github.com/mattn/go-sqlite3 v1.14.17
github.com/mattn/go-sqlite3 v1.14.6 github.com/microcosm-cc/bluemonday v1.0.25
github.com/microcosm-cc/bluemonday v1.0.5
github.com/mitchellh/go-wordwrap v1.0.1 github.com/mitchellh/go-wordwrap v1.0.1
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect github.com/stretchr/testify v1.8.4
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 // indirect github.com/urfave/cli/v2 v2.25.7
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect
github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.3.0
github.com/writeas/activity v0.1.2 github.com/writeas/activity v0.1.2
github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481 github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481
github.com/writeas/go-strip-markdown/v2 v2.1.1 github.com/writeas/go-strip-markdown/v2 v2.1.1
@ -39,11 +31,51 @@ require (
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219 github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320
github.com/writeas/slug v1.2.0 github.com/writeas/slug v1.2.0
github.com/writeas/web-core v1.3.1-0.20210330164422-95a3a717ed8f github.com/writeas/web-core v1.5.0
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b
github.com/writefreely/go-nodeinfo v1.2.0 github.com/writefreely/go-nodeinfo v1.2.0
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/crypto v0.13.0
golang.org/x/net v0.0.0-20200707034311-ab3426394381 golang.org/x/net v0.15.0
gopkg.in/ini.v1 v1.62.0 )
require (
code.as/core/socks v1.0.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beevik/etree v1.1.0 // indirect
github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1 // indirect
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect
github.com/clbanning/mxj v1.8.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe // indirect
github.com/go-test/deep v1.0.1 // indirect
github.com/gofrs/uuid v3.3.0+incompatible // indirect
github.com/gologme/log v1.2.0 // indirect
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect
github.com/gorilla/css v1.0.0 // indirect
github.com/gorilla/securecookie v1.1.1 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/jtolds/gls v4.2.1+incompatible // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // 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
github.com/writeas/go-writeas/v2 v2.0.2 // indirect
github.com/writeas/openssl-go v1.0.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
golang.org/x/sys v0.12.0 // indirect
golang.org/x/text v0.13.0 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
go 1.15 go 1.19

155
go.sum

@ -1,16 +1,11 @@
code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs= code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs=
code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY= code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY=
git.mills.io/prologic/go-gopher v0.0.0-20210712135410-b7ebb55feece h1:0esmnntqeuM1iBgHH0HOeSynsLA1l28p2K3h/WZuIfQ=
git.mills.io/prologic/go-gopher v0.0.0-20210712135410-b7ebb55feece/go.mod h1:EMXlYOIbYJQhPTtIltgaaHtCYDawV/HL0dYf8ShzAck=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beevik/etree v1.1.0 h1:T0xke/WvNtMoCqgzPhkX2r4rjY3GDZFi+FjpRZY2Jbs= 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/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1 h1:AFSJaASPGYNbkUa5c8ZybrcW9pP3Cy7+z5dnpcc/qG8= github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1 h1:AFSJaASPGYNbkUa5c8ZybrcW9pP3Cy7+z5dnpcc/qG8=
github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1/go.mod h1:EIlIeMufZ8nqdUhnesledB15xLRl4wIJUppwDLPrdrQ= github.com/captncraig/cors v0.0.0-20190703115713-e80254a89df1/go.mod h1:EIlIeMufZ8nqdUhnesledB15xLRl4wIJUppwDLPrdrQ=
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE= github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
@ -20,24 +15,26 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/clbanning/mxj v1.8.3/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/clbanning/mxj v1.8.3/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I=
github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs= github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5 h1:RAV05c0xOkJ3dZGS0JFybxFKZ2WMLabgx3uXnd7rpGs=
github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4= github.com/dchest/uniuri v0.0.0-20200228104902-7aecb25e1fe5/go.mod h1:GgB8SF9nRG+GqaDtLcwJZsQFhcogVCJ79j4EdT0c2V4=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 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-fed/httpsig v0.1.0/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4Lb71UuprPHqhjxGv3Jqonb9fgcaJH8= github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe h1:U71giCx5NjRn4Lb71UuprPHqhjxGv3Jqonb9fgcaJH8=
github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE= github.com/go-fed/httpsig v0.1.1-0.20200204213531-0ef28562fabe/go.mod h1:T56HUNYZUQ1AGUzhAYPugZfp36sKApVnGBgKlIY+aIE=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg= 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/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
@ -46,8 +43,8 @@ github.com/gologme/log v1.2.0 h1:Ya5Ip/KD6FX7uH0S31QO87nCCSucKtF44TLbTtO7V4c=
github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U= github.com/gologme/log v1.2.0/go.mod h1:gq31gQ8wEHkR+WekdWsqDuf8pXTUZA9BnnzTuPz1Y9U=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/csrf v1.7.0 h1:mMPjV5/3Zd460xCavIkppUdvnl5fPXMpv2uz2Zyg7/Y= github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE=
github.com/gorilla/csrf v1.7.0/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY= github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
@ -59,10 +56,10 @@ github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
github.com/guregu/null v3.5.0+incompatible h1:fSdvRTQtmBA4B4YDZXhLtxTIJZYuUxBFTTHS4B9djG4= github.com/guregu/null v4.0.0+incompatible h1:4zw0ckM7ECd6FNNddc3Fu4aty9nTlpkkzH7dPn4/4Gw=
github.com/guregu/null v3.5.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM= github.com/guregu/null v4.0.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
@ -72,43 +69,41 @@ github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw= github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2/go.mod h1:WtaVKD9TeruTED9ydiaOJU08qGoEPP/LyzTKiD3jEsw=
github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= 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/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= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec h1:ZXWuspqypleMuJy4bzYEqlMhJnGAYpLrWe5p7W3CdvI= github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec h1:ZXWuspqypleMuJy4bzYEqlMhJnGAYpLrWe5p7W3CdvI=
github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec/go.mod h1:voECJzdraJmolzPBgL9Z7ANwXf4oMXaTCsIkdiPpR/g= github.com/kylemcc/twitter-text-go v0.0.0-20180726194232-7f582f6736ec/go.mod h1:voECJzdraJmolzPBgL9Z7ANwXf4oMXaTCsIkdiPpR/g=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/manifoldco/promptui v0.8.0 h1:R95mMF+McvXZQ7j1g8ucVZE1gLP3Sv6j9vlF9kyRqQo= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/manifoldco/promptui v0.8.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/microcosm-cc/bluemonday v1.0.25 h1:4NEwSfiJ+Wva0VxN5B8OwMicaJvD8r9tlJWm9rtloEg=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/microcosm-cc/bluemonday v1.0.25/go.mod h1:ZIOjCQp1OrzBBPIJmfX4qDYFuhU02nx4bn030ixfHLE=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/microcosm-cc/bluemonday v1.0.5 h1:cF59UCKMmmUgqN1baLvqU/B1ZsMori+duLVTLpgiG3w=
github.com/microcosm-cc/bluemonday v1.0.5/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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 h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= 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/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= github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
@ -116,10 +111,11 @@ github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY= github.com/writeas/activity v0.1.2 h1:Y12B5lIrabfqKE7e7HFCWiXrlfXljr9tlkFm2mp7DgY=
github.com/writeas/activity v0.1.2/go.mod h1:mYYgiewmEM+8tlifirK/vl6tmB2EbjYaxwb+ndUw5T0= github.com/writeas/activity v0.1.2/go.mod h1:mYYgiewmEM+8tlifirK/vl6tmB2EbjYaxwb+ndUw5T0=
github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481 h1:BiSivIxLQFcKoUorpNN3rNwwFG5bITPnqUSyIccfdh0= github.com/writeas/activityserve v0.0.0-20200409150223-d7ab3eaa4481 h1:BiSivIxLQFcKoUorpNN3rNwwFG5bITPnqUSyIccfdh0=
@ -128,7 +124,6 @@ github.com/writeas/go-strip-markdown/v2 v2.1.1 h1:hAxUM21Uhznf/FnbVGiJciqzska6iL
github.com/writeas/go-strip-markdown/v2 v2.1.1/go.mod h1:UvvgPJgn1vvN8nWuE5e7v/+qmDu3BSVnKAB6Gl7hFzA= github.com/writeas/go-strip-markdown/v2 v2.1.1/go.mod h1:UvvgPJgn1vvN8nWuE5e7v/+qmDu3BSVnKAB6Gl7hFzA=
github.com/writeas/go-webfinger v1.1.0 h1:MzNyt0ry/GMsRmJGftn2o9mPwqK1Q5MLdh4VuJCfb1Q= github.com/writeas/go-webfinger v1.1.0 h1:MzNyt0ry/GMsRmJGftn2o9mPwqK1Q5MLdh4VuJCfb1Q=
github.com/writeas/go-webfinger v1.1.0/go.mod h1:w2VxyRO/J5vfNjJHYVubsjUGHd3RLDoVciz0DE3ApOc= github.com/writeas/go-webfinger v1.1.0/go.mod h1:w2VxyRO/J5vfNjJHYVubsjUGHd3RLDoVciz0DE3ApOc=
github.com/writeas/go-writeas v1.1.0 h1:WHGm6wriBkxYAOGbvriXH8DlMUGOi6jhSZLUZKQ+4mQ=
github.com/writeas/go-writeas v1.1.0/go.mod h1:oh9U1rWaiE0p3kzdKwwvOpNXgp0P0IELI7OLOwV4fkA= github.com/writeas/go-writeas v1.1.0/go.mod h1:oh9U1rWaiE0p3kzdKwwvOpNXgp0P0IELI7OLOwV4fkA=
github.com/writeas/go-writeas/v2 v2.0.2 h1:akvdMg89U5oBJiCkBwOXljVLTqP354uN6qnG2oOMrbk= github.com/writeas/go-writeas/v2 v2.0.2 h1:akvdMg89U5oBJiCkBwOXljVLTqP354uN6qnG2oOMrbk=
github.com/writeas/go-writeas/v2 v2.0.2/go.mod h1:9sjczQJKmru925fLzg0usrU1R1tE4vBmQtGnItUMR0M= github.com/writeas/go-writeas/v2 v2.0.2/go.mod h1:9sjczQJKmru925fLzg0usrU1R1tE4vBmQtGnItUMR0M=
@ -143,42 +138,74 @@ github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219 h1:baEp0631C8sT2r/h
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219/go.mod h1:NyM35ayknT7lzO6O/1JpfgGyv+0W9Z9q7aE0J8bXxfQ= github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219/go.mod h1:NyM35ayknT7lzO6O/1JpfgGyv+0W9Z9q7aE0J8bXxfQ=
github.com/writeas/openssl-go v1.0.0 h1:YXM1tDXeYOlTyJjoMlYLQH1xOloUimSR1WMF8kjFc5o= github.com/writeas/openssl-go v1.0.0 h1:YXM1tDXeYOlTyJjoMlYLQH1xOloUimSR1WMF8kjFc5o=
github.com/writeas/openssl-go v1.0.0/go.mod h1:WsKeK5jYl0B5y8ggOmtVjbmb+3rEGqSD25TppjJnETA= github.com/writeas/openssl-go v1.0.0/go.mod h1:WsKeK5jYl0B5y8ggOmtVjbmb+3rEGqSD25TppjJnETA=
github.com/writeas/saturday v1.6.0/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ= github.com/writeas/saturday v1.7.1/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 h1:PozPZ29CQ/xt6ym/+FvIz+KvKEObSSc5ye+95zbTjVU= github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320 h1:PozPZ29CQ/xt6ym/+FvIz+KvKEObSSc5ye+95zbTjVU=
github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ= github.com/writeas/saturday v1.7.2-0.20200427193424-392b95a03320/go.mod h1:ETE1EK6ogxptJpAgUbcJD0prAtX48bSloie80+tvnzQ=
github.com/writeas/slug v1.2.0 h1:EMQ+cwLiOcA6EtFwUgyw3Ge18x9uflUnOnR6bp/J+/g= 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/slug v1.2.0/go.mod h1:RE8shOqQP3YhsfsQe0L3RnuejfQ4Mk+JjY5YJQFubfQ=
github.com/writeas/web-core v1.3.1-0.20210330164422-95a3a717ed8f h1:ItBZYzdIbBmmqn8BZGWww00MBFgcUKy5ei0gJrzRDFk= github.com/writeas/web-core v1.5.0 h1:jKJObEgzYoNWUsIJmIDLAjN+vADkSsfcj7Ir9YyFcPg=
github.com/writeas/web-core v1.3.1-0.20210330164422-95a3a717ed8f/go.mod h1:DzNxa0YLV/wNeeWeHFPNa/nHmyJBFIIzXN/m9PpDm5c= github.com/writeas/web-core v1.5.0/go.mod h1:jb7dEvnyvPbKA3pTCzD1Dch5ou/n5YkkydZYhn6/NVY=
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b h1:h3NzB8OZ50NNi5k9yrFeyFszt3LyqyVK4+xUHFYY8B0=
github.com/writefreely/go-gopher v0.0.0-20220429181814-40127126f83b/go.mod h1:T2UVVzt+R5KSSZe2xRSytnwc2M9AoDegi7foeIsik+M=
github.com/writefreely/go-nodeinfo v1.2.0 h1:La+YbTCvmpTwFhBSlebWDDL81N88Qf/SCAvRLR7F8ss= 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= github.com/writefreely/go-nodeinfo v1.2.0/go.mod h1:UTvE78KpcjYOlRHupZIiSEFcXHioTXuacCbHU+CAcPg=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180527072434-ab813273cd59/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190131182504-b8fe1690c613/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180525142821-c11f84a56e43/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU= gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU= 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.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020 A Bunch Tell LLC. * Copyright © 2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -18,8 +18,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"git.mills.io/prologic/go-gopher"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
"github.com/writefreely/go-gopher"
) )
func initGopher(apper Apper) { func initGopher(apper Apper) {

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -20,10 +20,10 @@ import (
"strings" "strings"
"time" "time"
"git.mills.io/prologic/go-gopher"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
"github.com/writeas/impart" "github.com/writeas/impart"
"github.com/writeas/web-core/log" "github.com/writeas/web-core/log"
"github.com/writefreely/go-gopher"
"github.com/writefreely/writefreely/config" "github.com/writefreely/writefreely/config"
"github.com/writefreely/writefreely/page" "github.com/writefreely/writefreely/page"
) )
@ -155,8 +155,14 @@ func (h *Handler) User(f userHandlerFunc) http.HandlerFunc {
err := f(h.app.App(), u, w, r) err := f(h.app.App(), u, w, r)
if err == nil { if err == nil {
status = http.StatusOK status = http.StatusOK
} else if err, ok := err.(impart.HTTPError); ok { } else if impErr, ok := err.(impart.HTTPError); ok {
status = err.Status status = impErr.Status
if impErr == ErrUserNotFound {
log.Info("Logged-in user not found. Logging out.")
sendRedirect(w, http.StatusFound, "/me/logout?to="+h.app.App().cfg.App.LandingPath())
// Reset err so handleHTTPError does nothing
err = nil
}
} else { } else {
status = http.StatusInternalServerError status = http.StatusInternalServerError
} }

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019 A Bunch Tell LLC. * Copyright © 2018-2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019-2021 A Bunch Tell LLC. * Copyright © 2019-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -78,6 +78,9 @@ func handleViewUserInvites(app *App, u *User, w http.ResponseWriter, r *http.Req
p.Silenced, err = app.db.IsUserSilenced(u.ID) p.Silenced, err = app.db.IsUserSilenced(u.ID)
if err != nil { if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("view invites: %v", err) log.Error("view invites: %v", err)
} }

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019, 2021 A Bunch Tell LLC. * Copyright © 2019, 2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019, 2021 A Bunch Tell LLC. * Copyright © 2018-2019, 2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -695,6 +695,7 @@ table.downloads {
select.inputform, textarea.inputform { select.inputform, textarea.inputform {
border: 1px solid #999; border: 1px solid #999;
background: white;
} }
input, button, select.inputform, textarea.inputform, a.btn { input, button, select.inputform, textarea.inputform, a.btn {

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020 A Bunch Tell LLC. * Copyright © 2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019 A Bunch Tell LLC. * Copyright © 2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019 A Bunch Tell LLC. * Copyright © 2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -66,6 +66,7 @@ var migrations = []Migration{
New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0) New("support oauth via invite", oauthInvites), // V7 -> V8 (v0.12.0)
New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9 New("optimize drafts retrieval", optimizeDrafts), // V8 -> V9
New("support post signatures", supportPostSignatures), // V9 -> V10 New("support post signatures", supportPostSignatures), // V9 -> V10
New("Widen oauth_users.access_token", widenOauthAcceesToken), // V10 -> V11
} }
// CurrentVer returns the current migration version the application is on // CurrentVer returns the current migration version the application is on

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019 A Bunch Tell LLC. * Copyright © 2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020 A Bunch Tell LLC. * Copyright © 2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -0,0 +1,38 @@
/*
* Copyright © 2020 Musing Studio LLC.
*
* This file is part of WriteFreely.
*
* WriteFreely is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, included
* in the LICENSE file in this source code package.
*/
package migrations
/**
* Widen `oauth_users.access_token`, necessary only for mysql
*/
func widenOauthAcceesToken(db *datastore) error {
if db.driverName == driverMySQL {
t, err := db.Begin()
if err != nil {
t.Rollback()
return err
}
_, err = t.Exec(`ALTER TABLE oauth_users MODIFY COLUMN access_token ` + db.typeText() + db.collateMultiByte() + ` NULL`)
if err != nil {
t.Rollback()
return err
}
err = t.Commit()
if err != nil {
t.Rollback()
return err
}
}
return nil
}

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019 A Bunch Tell LLC. * Copyright © 2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019 A Bunch Tell LLC. * Copyright © 2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019-2021 A Bunch Tell LLC. * Copyright © 2019-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019-2021 A Bunch Tell LLC. * Copyright © 2019-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019-2020 A Bunch Tell LLC. * Copyright © 2019-2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020 A Bunch Tell LLC. * Copyright © 2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019, 2021 A Bunch Tell LLC. * Copyright © 2018-2019, 2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019-2021 A Bunch Tell LLC. * Copyright © 2019-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -293,10 +293,15 @@ func configureGiteaOauth(parentHandler *Handler, r *mux.Router, app *App) {
ClientID: app.Config().GiteaOauth.ClientID, ClientID: app.Config().GiteaOauth.ClientID,
ClientSecret: app.Config().GiteaOauth.ClientSecret, ClientSecret: app.Config().GiteaOauth.ClientSecret,
ExchangeLocation: app.Config().GiteaOauth.Host + "/login/oauth/access_token", ExchangeLocation: app.Config().GiteaOauth.Host + "/login/oauth/access_token",
InspectLocation: app.Config().GiteaOauth.Host + "/api/v1/user", InspectLocation: app.Config().GiteaOauth.Host + "/login/oauth/userinfo",
AuthLocation: app.Config().GiteaOauth.Host + "/login/oauth/authorize", AuthLocation: app.Config().GiteaOauth.Host + "/login/oauth/authorize",
HttpClient: config.DefaultHTTPClient(), HttpClient: config.DefaultHTTPClient(),
CallbackLocation: callbackLocation, CallbackLocation: callbackLocation,
Scope: "openid profile email",
MapUserID: "sub",
MapUsername: "login",
MapDisplayName: "full_name",
MapEmail: "email",
} }
configureOauthRoutes(parentHandler, r, app, oauthClient, callbackProxy) configureOauthRoutes(parentHandler, r, app, oauthClient, callbackProxy)
} }
@ -355,7 +360,7 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http
} }
if localUserID != -1 && attachUserID > 0 { if localUserID != -1 && attachUserID > 0 {
if err = addSessionFlash(app, w, r, "This Slack account is already attached to another user.", nil); err != nil { if err = addSessionFlash(app, w, r, "This OAuth account is already attached to another user.", nil); err != nil {
return impart.HTTPError{Status: http.StatusInternalServerError, Message: err.Error()} return impart.HTTPError{Status: http.StatusInternalServerError, Message: err.Error()}
} }
return impart.HTTPError{http.StatusFound, "/me/settings"} return impart.HTTPError{http.StatusFound, "/me/settings"}
@ -376,6 +381,7 @@ func (h oauthHandler) viewOauthCallback(app *App, w http.ResponseWriter, r *http
} }
if attachUserID > 0 { if attachUserID > 0 {
log.Info("attaching to user %d", attachUserID) log.Info("attaching to user %d", attachUserID)
log.Info("OAuth userid: %s", tokenInfo.UserID)
err = h.DB.RecordRemoteUserID(r.Context(), attachUserID, tokenInfo.UserID, provider, clientID, tokenResponse.AccessToken) err = h.DB.RecordRemoteUserID(r.Context(), attachUserID, tokenInfo.UserID, provider, clientID, tokenResponse.AccessToken)
if err != nil { if err != nil {
return impart.HTTPError{http.StatusInternalServerError, err.Error()} return impart.HTTPError{http.StatusInternalServerError, err.Error()}

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC and respective authors. * Copyright © 2020-2021 Musing Studio LLC and respective authors.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -3,6 +3,8 @@ package writefreely
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"github.com/writeas/web-core/log"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
@ -15,6 +17,11 @@ type giteaOauthClient struct {
ExchangeLocation string ExchangeLocation string
InspectLocation string InspectLocation string
CallbackLocation string CallbackLocation string
Scope string
MapUserID string
MapUsername string
MapDisplayName string
MapEmail string
HttpClient HttpClient HttpClient HttpClient
} }
@ -46,7 +53,7 @@ func (c giteaOauthClient) buildLoginURL(state string) (string, error) {
q.Set("redirect_uri", c.CallbackLocation) q.Set("redirect_uri", c.CallbackLocation)
q.Set("response_type", "code") q.Set("response_type", "code")
q.Set("state", state) q.Set("state", state)
// q.Set("scope", "read_user") q.Set("scope", c.Scope)
u.RawQuery = q.Encode() u.RawQuery = q.Encode()
return u.String(), nil return u.String(), nil
} }
@ -55,7 +62,7 @@ func (c giteaOauthClient) exchangeOauthCode(ctx context.Context, code string) (*
form := url.Values{} form := url.Values{}
form.Add("grant_type", "authorization_code") form.Add("grant_type", "authorization_code")
form.Add("redirect_uri", c.CallbackLocation) form.Add("redirect_uri", c.CallbackLocation)
// form.Add("scope", "read_user") form.Add("scope", c.Scope)
form.Add("code", code) form.Add("code", code)
req, err := http.NewRequest("POST", c.ExchangeLocation, strings.NewReader(form.Encode())) req, err := http.NewRequest("POST", c.ExchangeLocation, strings.NewReader(form.Encode()))
if err != nil { if err != nil {
@ -103,12 +110,24 @@ func (c giteaOauthClient) inspectOauthAccessToken(ctx context.Context, accessTok
return nil, errors.New("unable to inspect access token") return nil, errors.New("unable to inspect access token")
} }
var inspectResponse InspectResponse // since we don't know what the JSON from the server will look like, we create a
if err := limitedJsonUnmarshal(resp.Body, infoRequestMaxLen, &inspectResponse); err != nil { // generic interface and then map manually to values set in the config
var genericInterface map[string]interface{}
if err := limitedJsonUnmarshal(resp.Body, infoRequestMaxLen, &genericInterface); err != nil {
return nil, err return nil, err
} }
if inspectResponse.Error != "" {
return nil, errors.New(inspectResponse.Error) // map each relevant field in inspectResponse to the mapped field from the config
var inspectResponse InspectResponse
inspectResponse.UserID, _ = genericInterface[c.MapUserID].(string)
// log.Info("Userid from Gitea: %s", inspectResponse.UserID)
if inspectResponse.UserID == "" {
log.Error("[CONFIGURATION ERROR] Gitea OAuth provider returned empty UserID value (`%s`).\n Do you need to configure a different `map_user_id` value for this provider?", c.MapUserID)
return nil, fmt.Errorf("no UserID (`%s`) value returned", c.MapUserID)
} }
inspectResponse.Username, _ = genericInterface[c.MapUsername].(string)
inspectResponse.DisplayName, _ = genericInterface[c.MapDisplayName].(string)
inspectResponse.Email, _ = genericInterface[c.MapEmail].(string)
return &inspectResponse, nil return &inspectResponse, nil
} }

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019-2020 A Bunch Tell LLC. * Copyright © 2019-2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2019-2021 A Bunch Tell LLC. * Copyright © 2019-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -0,0 +1,10 @@
[provider_sect]
default = default_sect
legacy = legacy_sect
[default_sect]
activate = 1
[legacy_sect]
activate = 1

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -55,6 +55,9 @@ func handleViewPad(app *App, w http.ResponseWriter, r *http.Request) error {
} }
appData.Silenced, err = app.db.IsUserSilenced(appData.User.ID) appData.Silenced, err = app.db.IsUserSilenced(appData.User.ID)
if err != nil { if err != nil {
if err == ErrUserNotFound {
return err
}
log.Error("Unable to get user status for Pad: %v", err) log.Error("Unable to get user status for Pad: %v", err)
} }
} }

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019, 2021 A Bunch Tell LLC. * Copyright © 2018-2019, 2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -21,6 +21,7 @@ type StaticPage struct {
config.AppCfg config.AppCfg
Version string Version string
HeaderNav bool HeaderNav bool
CustomCSS bool
// Request values // Request values
Path string Path string

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019, 2021 A Bunch Tell LLC. * Copyright © 2018-2019, 2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,7 +1,6 @@
{{define "head"}}<title>Page not found &mdash; {{.SiteName}}</title>{{end}} {{define "head"}}<title>Page not found &mdash; {{.SiteName}}</title>{{end}}
{{define "content"}} {{define "content"}}
<div class="error-page"> <div class="error-page">
<p class="msg">This page is missing.</p> <p class="msg">Page not found.</p>
<p>Are you sure it was ever here?</p>
</div> </div>
{{end}} {{end}}

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2020 A Bunch Tell LLC. * Copyright © 2018-2020 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -120,7 +120,7 @@ func (p *PublicPost) augmentReadingDestination() {
} }
func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string { func applyMarkdown(data []byte, baseURL string, cfg *config.Config) string {
return applyMarkdownSpecial(data, false, baseURL, cfg) return applyMarkdownSpecial(data, baseURL, cfg, cfg.App.SingleUser)
} }
func disableYoutubeAutoplay(outHTML string) string { func disableYoutubeAutoplay(outHTML string) string {
@ -142,7 +142,7 @@ func disableYoutubeAutoplay(outHTML string) string {
return outHTML return outHTML
} }
func applyMarkdownSpecial(data []byte, skipNoFollow bool, baseURL string, cfg *config.Config) string { func applyMarkdownSpecial(data []byte, baseURL string, cfg *config.Config, skipNoFollow bool) string {
mdExtensions := 0 | mdExtensions := 0 |
blackfriday.EXTENSION_TABLES | blackfriday.EXTENSION_TABLES |
blackfriday.EXTENSION_FENCED_CODE | blackfriday.EXTENSION_FENCED_CODE |
@ -201,6 +201,9 @@ func applyBasicMarkdown(data []byte) string {
md := blackfriday.Markdown(append([]byte("# "), data...), blackfriday.HtmlRenderer(htmlFlags, "", ""), mdExtensions) md := blackfriday.Markdown(append([]byte("# "), data...), blackfriday.HtmlRenderer(htmlFlags, "", ""), mdExtensions)
// Remove H1 markup // Remove H1 markup
md = bytes.TrimSpace(md) // blackfriday.Markdown adds a newline at the end of the <h1> md = bytes.TrimSpace(md) // blackfriday.Markdown adds a newline at the end of the <h1>
if len(md) == 0 {
return ""
}
md = md[len("<h1>") : len(md)-len("</h1>")] md = md[len("<h1>") : len(md)-len("</h1>")]
// Strip out bad HTML // Strip out bad HTML
policy := bluemonday.UGCPolicy() policy := bluemonday.UGCPolicy()
@ -267,6 +270,7 @@ func getSanitizationPolicy() *bluemonday.Policy {
policy.AllowAttrs("target").OnElements("a") policy.AllowAttrs("target").OnElements("a")
policy.AllowAttrs("title").OnElements("abbr") policy.AllowAttrs("title").OnElements("abbr")
policy.AllowAttrs("style", "class", "id").Globally() policy.AllowAttrs("style", "class", "id").Globally()
policy.AllowAttrs("alt").OnElements("img")
policy.AllowElements("header", "footer") policy.AllowElements("header", "footer")
policy.AllowURLSchemes("http", "https", "mailto", "xmpp") policy.AllowURLSchemes("http", "https", "mailto", "xmpp")
return policy return policy
@ -281,12 +285,13 @@ func sanitizePost(content string) string {
// choosing what to generate. In case a post has a title, this function will // choosing what to generate. In case a post has a title, this function will
// fail, and logic should instead be implemented to skip this when there's no // fail, and logic should instead be implemented to skip this when there's no
// title, like so: // title, like so:
// var desc string //
// if title == "" { // var desc string
// desc = postDescription(content, title, friendlyId) // if title == "" {
// } else { // desc = postDescription(content, title, friendlyId)
// desc = shortPostDescription(content) // } else {
// } // desc = shortPostDescription(content)
// }
func postDescription(content, title, friendlyId string) string { func postDescription(content, title, friendlyId string) string {
maxLen := 140 maxLen := 140

@ -1,5 +1,5 @@
/* /*
* Copyright © 2021 A Bunch Tell LLC. * Copyright © 2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -1247,6 +1247,8 @@ func getSlug(title, lang string) string {
func getSlugFromPost(title, body, lang string) string { func getSlugFromPost(title, body, lang string) string {
if title == "" { if title == "" {
// Remove Markdown, so e.g. link URLs and image alt text don't make it into the slug
body = strings.TrimSpace(stripmd.StripOptions(body, stripmd.Options{SkipImages: true}))
title = postTitle(body, body) title = postTitle(body, body)
} }
title = parse.PostLede(title, false) title = parse.PostLede(title, false)

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -92,8 +92,8 @@ export const writeFreelyMarkdownSerializer = new MarkdownSerializer(
}, },
{ {
em: { em: {
open: "*", open: "_",
close: "*", close: "_",
mixable: true, mixable: true,
expelEnclosingWhitespace: true, expelEnclosingWhitespace: true,
}, },

@ -5,15 +5,16 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "prose",
"version": "1.0.0", "version": "1.0.0",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-preset-es2015": "^6.24.1", "babel-preset-es2015": "^6.24.1",
"markdown-it": "^12.0.4", "markdown-it": "^12.0.4",
"prosemirror-example-setup": "^1.1.2", "prosemirror-example-setup": "github:writefreely/prosemirror-example-setup",
"prosemirror-keymap": "^1.1.4", "prosemirror-keymap": "^1.1.4",
"prosemirror-markdown": "github:VV-EE/prosemirror-markdown", "prosemirror-markdown": "github:writefreely/prosemirror-markdown",
"prosemirror-model": "^1.9.1", "prosemirror-model": "^1.9.1",
"prosemirror-state": "^1.3.2", "prosemirror-state": "^1.3.2",
"prosemirror-view": "^1.14.2", "prosemirror-view": "^1.14.2",
@ -7160,8 +7161,8 @@
}, },
"node_modules/prosemirror-example-setup": { "node_modules/prosemirror-example-setup": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/prosemirror-example-setup/-/prosemirror-example-setup-1.1.2.tgz", "resolved": "git+ssh://git@github.com/writefreely/prosemirror-example-setup.git#aaea7d62ebc2fa769139faf8a097449441c8237c",
"integrity": "sha512-MTpIMyqk08jFnzxeRMCinCEMtVSTUtxKgQBGxfCbVe9C6zIOqp9qZZJz5Ojaad1GETySyuj8+OIHHvQsIaaaGQ==", "license": "MIT",
"dependencies": { "dependencies": {
"prosemirror-commands": "^1.0.0", "prosemirror-commands": "^1.0.0",
"prosemirror-dropcursor": "^1.0.0", "prosemirror-dropcursor": "^1.0.0",
@ -7214,7 +7215,9 @@
} }
}, },
"node_modules/prosemirror-markdown": { "node_modules/prosemirror-markdown": {
"resolved": "git+ssh://git@github.com/VV-EE/prosemirror-markdown.git#809d0a444cf4d366d7c0c350d881df1b55d172f2", "version": "1.5.1",
"resolved": "git+ssh://git@github.com/writefreely/prosemirror-markdown.git#809d0a444cf4d366d7c0c350d881df1b55d172f2",
"license": "MIT",
"dependencies": { "dependencies": {
"markdown-it": "^10.0.0", "markdown-it": "^10.0.0",
"prosemirror-model": "^1.0.0" "prosemirror-model": "^1.0.0"
@ -14934,9 +14937,8 @@
} }
}, },
"prosemirror-example-setup": { "prosemirror-example-setup": {
"version": "1.1.2", "version": "git+ssh://git@github.com/writefreely/prosemirror-example-setup.git#aaea7d62ebc2fa769139faf8a097449441c8237c",
"resolved": "https://registry.npmjs.org/prosemirror-example-setup/-/prosemirror-example-setup-1.1.2.tgz", "from": "prosemirror-example-setup@github:writefreely/prosemirror-example-setup",
"integrity": "sha512-MTpIMyqk08jFnzxeRMCinCEMtVSTUtxKgQBGxfCbVe9C6zIOqp9qZZJz5Ojaad1GETySyuj8+OIHHvQsIaaaGQ==",
"requires": { "requires": {
"prosemirror-commands": "^1.0.0", "prosemirror-commands": "^1.0.0",
"prosemirror-dropcursor": "^1.0.0", "prosemirror-dropcursor": "^1.0.0",
@ -14989,8 +14991,8 @@
} }
}, },
"prosemirror-markdown": { "prosemirror-markdown": {
"version": "git+ssh://git@github.com/VV-EE/prosemirror-markdown.git#809d0a444cf4d366d7c0c350d881df1b55d172f2", "version": "git+ssh://git@github.com/writefreely/prosemirror-markdown.git#809d0a444cf4d366d7c0c350d881df1b55d172f2",
"from": "prosemirror-markdown@github:VV-EE/prosemirror-markdown", "from": "prosemirror-markdown@github:writefreely/prosemirror-markdown",
"requires": { "requires": {
"markdown-it": "^10.0.0", "markdown-it": "^10.0.0",
"prosemirror-model": "^1.0.0" "prosemirror-model": "^1.0.0"

@ -83,6 +83,14 @@ class ProseMirrorView {
typingTimer = setTimeout(doneTyping, doneTypingInterval); typingTimer = setTimeout(doneTyping, doneTypingInterval);
this.updateState(newState); this.updateState(newState);
}, },
handleDOMEvents: {
drop: (view, event) => {
// If a file is dropped externally into the editor, do not insert anything. This will not trigger if an image has been inserted after upload and is dragged and dropped internally to change its position.
if (event.dataTransfer.files.length > 0) {
event.preventDefault();
}
}
},
}); });
// Editor is focused to the last position. This is a workaround for a bug: // Editor is focused to the last position. This is a workaround for a bug:
// 1. 1 type something in an existing entry // 1. 1 type something in an existing entry

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018 A Bunch Tell LLC. * Copyright © 2018 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -219,6 +219,7 @@ func RouteCollections(handler *Handler, r *mux.Router) {
r.HandleFunc("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional)) r.HandleFunc("/lang:{lang:[a-z]{2}}", handler.Web(handleViewCollectionLang, UserLevelOptional))
r.HandleFunc("/lang:{lang:[a-z]{2}}/page/{page:[0-9]+}", handler.Web(handleViewCollectionLang, UserLevelOptional)) r.HandleFunc("/lang:{lang:[a-z]{2}}/page/{page:[0-9]+}", handler.Web(handleViewCollectionLang, UserLevelOptional))
r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader)) r.HandleFunc("/tag:{tag}", handler.Web(handleViewCollectionTag, UserLevelReader))
r.HandleFunc("/tag:{tag}/page/{page:[0-9]+}", handler.Web(handleViewCollectionTag, UserLevelReader))
r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader)) r.HandleFunc("/tag:{tag}/feed/", handler.Web(ViewFeed, UserLevelReader))
r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap)) r.HandleFunc("/sitemap.xml", handler.AllReader(handleViewSitemap))
r.HandleFunc("/feed/", handler.AllReader(ViewFeed)) r.HandleFunc("/feed/", handler.AllReader(ViewFeed))

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# #
# Copyright © 2020 A Bunch Tell LLC. # Copyright © 2020 Musing Studio LLC.
# #
# This file is part of WriteFreely. # This file is part of WriteFreely.
# #

@ -11,7 +11,7 @@
## have not installed the binary `writefreely` in another location. ## ## have not installed the binary `writefreely` in another location. ##
############################################################################### ###############################################################################
# #
# Copyright © 2019-2020 A Bunch Tell LLC. # Copyright © 2019-2020 Musing Studio LLC.
# #
# This file is part of WriteFreely. # This file is part of WriteFreely.
# #

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019 A Bunch Tell LLC. * Copyright © 2018-2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -130,12 +130,13 @@ func saveUserSession(app *App, r *http.Request, w http.ResponseWriter) error {
return err return err
} }
func getFullUserSession(app *App, r *http.Request) *User { func getFullUserSession(app *App, r *http.Request) (*User, error) {
u := getUserSession(app, r) u := getUserSession(app, r)
if u == nil { if u == nil {
return nil return nil, nil
} }
u, _ = app.db.GetUserByID(u.ID) var err error
return u u, err = app.db.GetUserByID(u.ID)
return u, err
} }

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2019 A Bunch Tell LLC. * Copyright © 2018-2019 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2016-2021 A Bunch Tell LLC. * Copyright © 2016-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2020-2021 A Bunch Tell LLC. * Copyright © 2020-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *

@ -1,5 +1,5 @@
/* /*
* Copyright © 2018-2021 A Bunch Tell LLC. * Copyright © 2018-2021 Musing Studio LLC.
* *
* This file is part of WriteFreely. * This file is part of WriteFreely.
* *
@ -135,7 +135,10 @@ func InitTemplates(cfg *config.Config) error {
log.Info("Loading pages...") log.Info("Loading pages...")
// Initialize all static pages that use the base template // Initialize all static pages that use the base template
filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, pagesDir), func(path string, i os.FileInfo, err error) error { err = filepath.Walk(filepath.Join(cfg.Server.PagesParentDir, pagesDir), func(path string, i os.FileInfo, err error) error {
if err != nil {
return err
}
if !i.IsDir() && !strings.HasPrefix(i.Name(), ".") { if !i.IsDir() && !strings.HasPrefix(i.Name(), ".") {
key := i.Name() key := i.Name()
initPage(cfg.Server.PagesParentDir, path, key) initPage(cfg.Server.PagesParentDir, path, key)
@ -143,10 +146,16 @@ func InitTemplates(cfg *config.Config) error {
return nil return nil
}) })
if err != nil {
return err
}
log.Info("Loading user pages...") log.Info("Loading user pages...")
// Initialize all user pages that use base templates // Initialize all user pages that use base templates
filepath.Walk(filepath.Join(cfg.Server.TemplatesParentDir, templatesDir, "user"), func(path string, f os.FileInfo, err error) error { err = filepath.Walk(filepath.Join(cfg.Server.TemplatesParentDir, templatesDir, "user"), func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") { if !f.IsDir() && !strings.HasPrefix(f.Name(), ".") {
corePath := path corePath := path
if cfg.Server.TemplatesParentDir != "" { if cfg.Server.TemplatesParentDir != "" {
@ -162,6 +171,9 @@ func InitTemplates(cfg *config.Config) error {
return nil return nil
}) })
if err != nil {
return err
}
return nil return nil
} }

@ -5,6 +5,7 @@
<title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} &mdash; {{.SiteName}}</title> <title>{{if .Editing}}Editing {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}}{{else}}New Post{{end}} &mdash; {{.SiteName}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="google" value="notranslate"> <meta name="google" value="notranslate">

@ -2,8 +2,9 @@
<html> <html>
<head> <head>
{{ template "head" . }} {{ template "head" . }}
<link rel="stylesheet" type="text/css" href="{{.Host}}/css/{{.Theme}}.css" /> <link rel="stylesheet" type="text/css" href="/css/{{.Theme}}.css" />
<link rel="shortcut icon" href="{{.Host}}/favicon.ico" /> {{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="application-name" content="{{.SiteName}}"> <meta name="application-name" content="{{.SiteName}}">
@ -86,7 +87,7 @@
{{end}} {{end}}
</script> </script>
{{else}} {{else}}
{{if .WebFonts}}<link href="{{.Host}}/css/fonts.css" rel="stylesheet" type="text/css" />{{end}} {{if .WebFonts}}<link href="/css/fonts.css" rel="stylesheet" type="text/css" />{{end}}
{{end}} {{end}}
</body> </body>
</html>{{end}} </html>{{end}}

@ -6,6 +6,7 @@
<title>{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}}</title> <title>{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" /> <link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="canonical" href="{{.CanonicalURL .Host}}" /> <link rel="canonical" href="{{.CanonicalURL .Host}}" />

@ -6,6 +6,7 @@
<title>{{.DisplayTitle}}{{if not .SingleUser}} &mdash; {{.SiteName}}{{end}}</title> <title>{{.DisplayTitle}}{{if not .SingleUser}} &mdash; {{.SiteName}}{{end}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" /> <link rel="shortcut icon" href="/favicon.ico" />
<link rel="canonical" href="{{.CanonicalURL}}"> <link rel="canonical" href="{{.CanonicalURL}}">
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}} {{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}

@ -6,6 +6,7 @@
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
<link rel="stylesheet" type="text/css" href="/css/prose.css" /> <link rel="stylesheet" type="text/css" href="/css/prose.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="google" value="notranslate"> <meta name="google" value="notranslate">

@ -6,6 +6,7 @@
<title>{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}}</title> <title>{{.PlainDisplayTitle}} {{localhtml "title dash" .Language.String}} {{.Collection.DisplayTitle}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" /> <link rel="shortcut icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
{{ if .IsFound }} {{ if .IsFound }}

@ -6,6 +6,7 @@
<title>{{.Tag}} &mdash; {{.Collection.DisplayTitle}}</title> <title>{{.Tag}} &mdash; {{.Collection.DisplayTitle}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" /> <link rel="shortcut icon" href="/favicon.ico" />
{{if not .Collection.IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.Tag}} posts on {{.DisplayTitle}}" href="{{.CanonicalURL}}tag:{{.Tag}}/feed/" />{{end}} {{if not .Collection.IsPrivate}}<link rel="alternate" type="application/rss+xml" title="{{.Tag}} posts on {{.DisplayTitle}}" href="{{.CanonicalURL}}tag:{{.Tag}}/feed/" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -60,6 +61,17 @@
{{if .Posts}}<section id="wrapper" itemscope itemtype="http://schema.org/Blog">{{else}}<div id="wrapper">{{end}} {{if .Posts}}<section id="wrapper" itemscope itemtype="http://schema.org/Blog">{{else}}<div id="wrapper">{{end}}
<h1>{{.Tag}}</h1> <h1>{{.Tag}}</h1>
{{template "posts" .}} {{template "posts" .}}
{{if gt .TotalPages 1}}<nav id="paging" class="content-container clearfix">
{{if or (and .Format.Ascending (lt .CurrentPage .TotalPages)) (isRTL .Direction)}}
{{if gt .CurrentPage 1}}<a href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">&#8672; {{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Previous{{else}}Newer{{end}}</a>{{end}}
{{if lt .CurrentPage .TotalPages}}<a style="float:right;" href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">{{if and .Format.Ascending (lt .CurrentPage .TotalPages)}}Next{{else}}Older{{end}} &#8674;</a>{{end}}
{{else}}
{{if lt .CurrentPage .TotalPages}}<a href="{{.NextPageURL .Prefix .CurrentPage .IsTopLevel}}">&#8672; Older</a>{{end}}
{{if gt .CurrentPage 1}}<a style="float:right;" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">Newer &#8674;</a>{{end}}
{{end}}
</nav>{{end}}
{{if .Posts}}</section>{{else}}</div>{{end}} {{if .Posts}}</section>{{else}}</div>{{end}}
{{ if .Collection.ShowFooterBranding }} {{ if .Collection.ShowFooterBranding }}

@ -6,6 +6,7 @@
<title>{{.DisplayTitle}}{{if not .SingleUser}} &mdash; {{.SiteName}}{{end}}</title> <title>{{.DisplayTitle}}{{if not .SingleUser}} &mdash; {{.SiteName}}{{end}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<link rel="shortcut icon" href="/favicon.ico" /> <link rel="shortcut icon" href="/favicon.ico" />
<link rel="canonical" href="{{.CanonicalURL}}"> <link rel="canonical" href="{{.CanonicalURL}}">
{{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}} {{if gt .CurrentPage 1}}<link rel="prev" href="{{.PrevPageURL .Prefix .CurrentPage .IsTopLevel}}">{{end}}
@ -14,18 +15,18 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="generator" content="WriteFreely"> <meta name="generator" content="WriteFreely">
<meta name="description" content="{{.Description}}"> <meta name="description" content="{{.PlainDescription}}">
<meta itemprop="name" content="{{.DisplayTitle}}"> <meta itemprop="name" content="{{.DisplayTitle}}">
<meta itemprop="description" content="{{.Description}}"> <meta itemprop="description" content="{{.PlainDescription}}">
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
<meta name="twitter:title" content="{{.DisplayTitle}}"> <meta name="twitter:title" content="{{.DisplayTitle}}">
<meta name="twitter:image" content="{{.AvatarURL}}"> <meta name="twitter:image" content="{{.AvatarURL}}">
<meta name="twitter:description" content="{{.Description}}"> <meta name="twitter:description" content="{{.PlainDescription}}">
<meta property="og:title" content="{{.DisplayTitle}}" /> <meta property="og:title" content="{{.DisplayTitle}}" />
<meta property="og:site_name" content="{{.DisplayTitle}}" /> <meta property="og:site_name" content="{{.DisplayTitle}}" />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:url" content="{{.CanonicalURL}}" /> <meta property="og:url" content="{{.CanonicalURL}}" />
<meta property="og:description" content="{{.Description}}" /> <meta property="og:description" content="{{.PlainDescription}}" />
<meta property="og:image" content="{{.AvatarURL}}"> <meta property="og:image" content="{{.AvatarURL}}">
{{template "collection-meta" .}} {{template "collection-meta" .}}
{{if .StyleSheet}}<style type="text/css">{{.StyleSheetDisplay}}</style>{{end}} {{if .StyleSheet}}<style type="text/css">{{.StyleSheetDisplay}}</style>{{end}}
@ -82,7 +83,7 @@
{{template "user-silenced"}} {{template "user-silenced"}}
{{end}} {{end}}
<h1 dir="{{.Direction}}" id="blog-title">{{if .Posts}}{{else}}<span class="writeas-prefix"><a href="/">write.as</a></span> {{end}}<a href="/{{if .IsTopLevel}}{{else}}{{.Prefix}}{{.Alias}}/{{end}}" class="h-card p-author u-url" rel="me author">{{.DisplayTitle}}</a></h1> <h1 dir="{{.Direction}}" id="blog-title">{{if .Posts}}{{else}}<span class="writeas-prefix"><a href="/">write.as</a></span> {{end}}<a href="/{{if .IsTopLevel}}{{else}}{{.Prefix}}{{.Alias}}/{{end}}" class="h-card p-author u-url" rel="me author">{{.DisplayTitle}}</a></h1>
{{if .Description}}<p class="description p-note">{{.Description}}</p>{{end}} {{if .Description}}<p class="description p-note">{{.DisplayDescription}}</p>{{end}}
{{/*if not .Public/*}} {{/*if not .Public/*}}
<!--p class="meta-note"><span>Private collection</span>. Only you can see this page.</p--> <!--p class="meta-note"><span>Private collection</span>. Only you can see this page.</p-->
{{/*end*/}} {{/*end*/}}

@ -5,6 +5,7 @@
<title>Edit metadata: {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}} &mdash; {{.SiteName}}</title> <title>Edit metadata: {{if .Post.Title}}{{.Post.Title}}{{else}}{{.Post.Id}}{{end}} &mdash; {{.SiteName}}</title>
<link rel="stylesheet" type="text/css" href="/css/write.css" /> <link rel="stylesheet" type="text/css" href="/css/write.css" />
{{if .CustomCSS}}<link rel="stylesheet" type="text/css" href="/local/custom.css" />{{end}}
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style type="text/css"> <style type="text/css">
dt { dt {
@ -65,7 +66,7 @@
{{end}} {{end}}
<dt><label for="lang">Language</label></dt> <dt><label for="lang">Language</label></dt>
<dd> <dd>
<select name="lang" id="lang" dir="auto"> <select name="lang" id="lang" dir="auto" class="inputform">
<option value=""></option> <option value=""></option>
<option value="ab"{{if eq "ab" .Post.Language.String}} selected="selected"{{end}}>аҧсуа бызшәа, аҧсшәа</option> <option value="ab"{{if eq "ab" .Post.Language.String}} selected="selected"{{end}}>аҧсуа бызшәа, аҧсшәа</option>
<option value="aa"{{if eq "aa" .Post.Language.String}} selected="selected"{{end}}>Afaraf</option> <option value="aa"{{if eq "aa" .Post.Language.String}} selected="selected"{{end}}>Afaraf</option>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save