Merge pull request #172 from writeas/import-text
add basic text file imports Resolves T609
This commit is contained in:
commit
75e2b60328
|
@ -0,0 +1,195 @@
|
||||||
|
package writefreely
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/writeas/impart"
|
||||||
|
wfimport "github.com/writeas/import"
|
||||||
|
"github.com/writeas/web-core/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func viewImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
// Fetch extra user data
|
||||||
|
p := NewUserPage(app, r, u, "Import Posts", nil)
|
||||||
|
|
||||||
|
c, err := app.db.GetCollections(u, app.Config().App.Host)
|
||||||
|
if err != nil {
|
||||||
|
return impart.HTTPError{http.StatusInternalServerError, fmt.Sprintf("unable to fetch collections: %v", err)}
|
||||||
|
}
|
||||||
|
|
||||||
|
d := struct {
|
||||||
|
*UserPage
|
||||||
|
Collections *[]Collection
|
||||||
|
Flashes []template.HTML
|
||||||
|
Message string
|
||||||
|
InfoMsg bool
|
||||||
|
}{
|
||||||
|
UserPage: p,
|
||||||
|
Collections: c,
|
||||||
|
Flashes: []template.HTML{},
|
||||||
|
}
|
||||||
|
|
||||||
|
flashes, _ := getSessionFlashes(app, w, r, nil)
|
||||||
|
for _, flash := range flashes {
|
||||||
|
if strings.HasPrefix(flash, "SUCCESS: ") {
|
||||||
|
d.Message = strings.TrimPrefix(flash, "SUCCESS: ")
|
||||||
|
} else if strings.HasPrefix(flash, "INFO: ") {
|
||||||
|
d.Message = strings.TrimPrefix(flash, "INFO: ")
|
||||||
|
d.InfoMsg = true
|
||||||
|
} else {
|
||||||
|
d.Flashes = append(d.Flashes, template.HTML(flash))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showUserPage(w, "import", d)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleImport(app *App, u *User, w http.ResponseWriter, r *http.Request) error {
|
||||||
|
// limit 10MB per submission
|
||||||
|
r.ParseMultipartForm(10 << 20)
|
||||||
|
|
||||||
|
collAlias := r.PostFormValue("collection")
|
||||||
|
coll := &Collection{
|
||||||
|
ID: 0,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if collAlias != "" {
|
||||||
|
coll, err = app.db.GetCollection(collAlias)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Unable to get collection for import: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Only allow uploading to collection if current user is owner
|
||||||
|
if coll.OwnerID != u.ID {
|
||||||
|
err := ErrUnauthorizedGeneral
|
||||||
|
_ = addSessionFlash(app, w, r, err.Message, nil)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
coll.hostName = app.cfg.App.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
fileDates := make(map[string]int64)
|
||||||
|
err = json.Unmarshal([]byte(r.FormValue("fileDates")), &fileDates)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("invalid form data for file dates: %v", err)
|
||||||
|
return impart.HTTPError{http.StatusBadRequest, "form data for file dates was invalid"}
|
||||||
|
}
|
||||||
|
files := r.MultipartForm.File["files"]
|
||||||
|
var fileErrs []error
|
||||||
|
filesSubmitted := len(files)
|
||||||
|
var filesImported int
|
||||||
|
for _, formFile := range files {
|
||||||
|
fname := ""
|
||||||
|
ok := func() bool {
|
||||||
|
file, err := formFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
fileErrs = append(fileErrs, fmt.Errorf("Unable to read file %s", formFile.Filename))
|
||||||
|
log.Error("import file: open from form: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
tempFile, err := ioutil.TempFile("", "post-upload-*.txt")
|
||||||
|
if err != nil {
|
||||||
|
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
|
||||||
|
log.Error("import file: create temp file %s: %v", formFile.Filename, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer tempFile.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(tempFile, file)
|
||||||
|
if err != nil {
|
||||||
|
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
|
||||||
|
log.Error("import file: copy to temp location %s: %v", formFile.Filename, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := tempFile.Stat()
|
||||||
|
if err != nil {
|
||||||
|
fileErrs = append(fileErrs, fmt.Errorf("Internal error for %s", formFile.Filename))
|
||||||
|
log.Error("import file: stat temp file %s: %v", formFile.Filename, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fname = info.Name()
|
||||||
|
return true
|
||||||
|
}()
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
post, err := wfimport.FromFile(filepath.Join(os.TempDir(), fname))
|
||||||
|
if err == wfimport.ErrEmptyFile {
|
||||||
|
// not a real error so don't log
|
||||||
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("%s was empty, import skipped", formFile.Filename), nil)
|
||||||
|
continue
|
||||||
|
} else if err == wfimport.ErrInvalidContentType {
|
||||||
|
// same as above
|
||||||
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("%s is not a supported post file", formFile.Filename), nil)
|
||||||
|
continue
|
||||||
|
} else if err != nil {
|
||||||
|
fileErrs = append(fileErrs, fmt.Errorf("failed to read copy of %s", formFile.Filename))
|
||||||
|
log.Error("import textfile: file to post: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if collAlias != "" {
|
||||||
|
post.Collection = collAlias
|
||||||
|
}
|
||||||
|
dateTime := time.Unix(fileDates[formFile.Filename], 0)
|
||||||
|
post.Created = &dateTime
|
||||||
|
created := post.Created.Format("2006-01-02T15:04:05Z")
|
||||||
|
submittedPost := SubmittedPost{
|
||||||
|
Title: &post.Title,
|
||||||
|
Content: &post.Content,
|
||||||
|
Font: "norm",
|
||||||
|
Created: &created,
|
||||||
|
}
|
||||||
|
rp, err := app.db.CreatePost(u.ID, coll.ID, &submittedPost)
|
||||||
|
if err != nil {
|
||||||
|
fileErrs = append(fileErrs, fmt.Errorf("failed to create post from %s", formFile.Filename))
|
||||||
|
log.Error("import textfile: create db post: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Federate post, if necessary
|
||||||
|
if app.cfg.App.Federation && coll.ID > 0 {
|
||||||
|
go federatePost(
|
||||||
|
app,
|
||||||
|
&PublicPost{
|
||||||
|
Post: rp,
|
||||||
|
Collection: &CollectionObj{
|
||||||
|
Collection: *coll,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
coll.ID,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
filesImported++
|
||||||
|
}
|
||||||
|
if len(fileErrs) != 0 {
|
||||||
|
_ = addSessionFlash(app, w, r, multierror.ListFormatFunc(fileErrs), nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if filesImported == filesSubmitted {
|
||||||
|
verb := "posts"
|
||||||
|
if filesSubmitted == 1 {
|
||||||
|
verb = "post"
|
||||||
|
}
|
||||||
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("SUCCESS: Import complete, %d %s imported.", filesImported, verb), nil)
|
||||||
|
} else if filesImported > 0 {
|
||||||
|
_ = addSessionFlash(app, w, r, fmt.Sprintf("INFO: %d of %d posts imported, see details below.", filesImported, filesSubmitted), nil)
|
||||||
|
}
|
||||||
|
return impart.HTTPError{http.StatusFound, "/me/import"}
|
||||||
|
}
|
3
go.mod
3
go.mod
|
@ -17,6 +17,8 @@ require (
|
||||||
github.com/gorilla/schema v1.0.2
|
github.com/gorilla/schema v1.0.2
|
||||||
github.com/gorilla/sessions v1.1.3
|
github.com/gorilla/sessions v1.1.3
|
||||||
github.com/guregu/null v3.4.0+incompatible
|
github.com/guregu/null v3.4.0+incompatible
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0
|
||||||
|
github.com/ikeikeikeike/go-sitemap-generator v1.0.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/jtolds/gls v4.2.1+incompatible // indirect
|
||||||
github.com/kr/pretty v0.1.0
|
github.com/kr/pretty v0.1.0
|
||||||
|
@ -40,6 +42,7 @@ require (
|
||||||
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2
|
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2
|
||||||
github.com/writeas/httpsig v1.0.0
|
github.com/writeas/httpsig v1.0.0
|
||||||
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d
|
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d
|
||||||
|
github.com/writeas/import v0.2.0
|
||||||
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219
|
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219
|
||||||
github.com/writeas/nerds v1.0.0
|
github.com/writeas/nerds v1.0.0
|
||||||
github.com/writeas/saturday v1.7.1
|
github.com/writeas/saturday v1.7.1
|
||||||
|
|
20
go.sum
20
go.sum
|
@ -1,3 +1,5 @@
|
||||||
|
code.as/core/socks v1.0.0 h1:SPQXNp4SbEwjOAP9VzUahLHak8SDqy5n+9cm9tpjZOs=
|
||||||
|
code.as/core/socks v1.0.0/go.mod h1:BAXBy5O9s2gmw6UxLqNJcVbWY7C/UPs+801CcSsfWOY=
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk=
|
||||||
|
@ -58,6 +60,12 @@ github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9R
|
||||||
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||||
github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM=
|
github.com/guregu/null v3.4.0+incompatible h1:a4mw37gBO7ypcBlTJeZGuMpSxxFTV9qFfFKgWxQSGaM=
|
||||||
github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
|
github.com/guregu/null v3.4.0+incompatible/go.mod h1:ePGpQaN9cw0tj45IR5E5ehMvsFlLlQZAkkOXZurJ3NM=
|
||||||
|
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/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o=
|
||||||
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
|
github.com/ikeikeikeike/go-sitemap-generator v1.0.1 h1:49Fn8gro/B12vCY8pf5/+/Jpr3kwB9TvP0MSymo69SY=
|
||||||
|
github.com/ikeikeikeike/go-sitemap-generator v1.0.1/go.mod h1:QI+zWsz6yQyxkG9LWNcnu0f7aiAE5tPdsZOsICgmd1c=
|
||||||
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6ejs3Ut+jmsYvuOoAW2pSM=
|
github.com/ikeikeikeike/go-sitemap-generator/v2 v2.0.2 h1:wIdDEle9HEy7vBPjC6oKz6ejs3Ut+jmsYvuOoAW2pSM=
|
||||||
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=
|
||||||
|
@ -119,12 +127,24 @@ github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6Fk
|
||||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
|
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
|
||||||
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2 h1:DUsp4OhdfI+e6iUqcPQlwx8QYXuUDsToTz/x82D3Zuo=
|
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2 h1:DUsp4OhdfI+e6iUqcPQlwx8QYXuUDsToTz/x82D3Zuo=
|
||||||
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2/go.mod h1:w2VxyRO/J5vfNjJHYVubsjUGHd3RLDoVciz0DE3ApOc=
|
github.com/writeas/go-webfinger v0.0.0-20190106002315-85cf805c86d2/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/v2 v2.0.2 h1:akvdMg89U5oBJiCkBwOXljVLTqP354uN6qnG2oOMrbk=
|
||||||
|
github.com/writeas/go-writeas/v2 v2.0.2/go.mod h1:9sjczQJKmru925fLzg0usrU1R1tE4vBmQtGnItUMR0M=
|
||||||
github.com/writeas/httpsig v1.0.0 h1:peIAoIA3DmlP8IG8tMNZqI4YD1uEnWBmkcC9OFPjt3A=
|
github.com/writeas/httpsig v1.0.0 h1:peIAoIA3DmlP8IG8tMNZqI4YD1uEnWBmkcC9OFPjt3A=
|
||||||
github.com/writeas/httpsig v1.0.0/go.mod h1:7ClMGSrSVXJbmiLa17bZ1LrG1oibGZmUMlh3402flPY=
|
github.com/writeas/httpsig v1.0.0/go.mod h1:7ClMGSrSVXJbmiLa17bZ1LrG1oibGZmUMlh3402flPY=
|
||||||
github.com/writeas/impart v1.1.0 h1:nPnoO211VscNkp/gnzir5UwCDEvdHThL5uELU60NFSE=
|
github.com/writeas/impart v1.1.0 h1:nPnoO211VscNkp/gnzir5UwCDEvdHThL5uELU60NFSE=
|
||||||
github.com/writeas/impart v1.1.0/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
|
github.com/writeas/impart v1.1.0/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
|
||||||
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d h1:PK7DOj3JE6MGf647esPrKzXEHFjGWX2hl22uX79ixaE=
|
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d h1:PK7DOj3JE6MGf647esPrKzXEHFjGWX2hl22uX79ixaE=
|
||||||
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
|
github.com/writeas/impart v1.1.1-0.20191230230525-d3c45ced010d/go.mod h1:g0MpxdnTOHHrl+Ca/2oMXUHJ0PcRAEWtkCzYCJUXC9Y=
|
||||||
|
github.com/writeas/import v0.0.0-20190815214647-baae8acd8d06 h1:S6oKKP8GhSoyZUvVuhO9UiQ9f+U1aR/x5B4MP7YQHaU=
|
||||||
|
github.com/writeas/import v0.0.0-20190815214647-baae8acd8d06/go.mod h1:f3K8z7YnJwKnPIT4h7980n9C6cQb4DIB2QcxVCTB7lE=
|
||||||
|
github.com/writeas/import v0.0.0-20190815235139-628d10daaa9e h1:31PkvDTWkjzC1nGzWw9uAE92ZfcVyFX/K9L9ejQjnEs=
|
||||||
|
github.com/writeas/import v0.0.0-20190815235139-628d10daaa9e/go.mod h1:f3K8z7YnJwKnPIT4h7980n9C6cQb4DIB2QcxVCTB7lE=
|
||||||
|
github.com/writeas/import v0.1.1 h1:SbYltT+nxrJBUe0xQWJqeKMHaupbxV0a6K3RtwcE4yY=
|
||||||
|
github.com/writeas/import v0.1.1/go.mod h1:gFe0Pl7ZWYiXbI0TJxeMMyylPGZmhVvCfQxhMEc8CxM=
|
||||||
|
github.com/writeas/import v0.2.0 h1:Ov23JW9Rnjxk06rki1Spar45bNX647HhwhAZj3flJiY=
|
||||||
|
github.com/writeas/import v0.2.0/go.mod h1:gFe0Pl7ZWYiXbI0TJxeMMyylPGZmhVvCfQxhMEc8CxM=
|
||||||
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219 h1:baEp0631C8sT2r/hqwypIw2snCFZa6h7U6TojoLHu/c=
|
github.com/writeas/monday v0.0.0-20181024183321-54a7dd579219 h1:baEp0631C8sT2r/hqwypIw2snCFZa6h7U6TojoLHu/c=
|
||||||
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/nerds v1.0.0 h1:ZzRcCN+Sr3MWID7o/x1cr1ZbLvdpej9Y1/Ho+JKlqxo=
|
github.com/writeas/nerds v1.0.0 h1:ZzRcCN+Sr3MWID7o/x1cr1ZbLvdpej9Y1/Ho+JKlqxo=
|
||||||
|
|
|
@ -1318,6 +1318,24 @@ form {
|
||||||
font-size: 0.86em;
|
font-size: 0.86em;
|
||||||
line-height: 2;
|
line-height: 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.prominent {
|
||||||
|
margin: 1em 0;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
input, select {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
div.row {
|
div.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -97,6 +97,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
||||||
me.HandleFunc("/posts/export.json", handler.Download(viewExportPosts, UserLevelUser)).Methods("GET")
|
me.HandleFunc("/posts/export.json", handler.Download(viewExportPosts, UserLevelUser)).Methods("GET")
|
||||||
me.HandleFunc("/export", handler.User(viewExportOptions)).Methods("GET")
|
me.HandleFunc("/export", handler.User(viewExportOptions)).Methods("GET")
|
||||||
me.HandleFunc("/export.json", handler.Download(viewExportFull, UserLevelUser)).Methods("GET")
|
me.HandleFunc("/export.json", handler.Download(viewExportFull, UserLevelUser)).Methods("GET")
|
||||||
|
me.HandleFunc("/import", handler.User(viewImport)).Methods("GET")
|
||||||
me.HandleFunc("/settings", handler.User(viewSettings)).Methods("GET")
|
me.HandleFunc("/settings", handler.User(viewSettings)).Methods("GET")
|
||||||
me.HandleFunc("/invites", handler.User(handleViewUserInvites)).Methods("GET")
|
me.HandleFunc("/invites", handler.User(handleViewUserInvites)).Methods("GET")
|
||||||
me.HandleFunc("/logout", handler.Web(viewLogout, UserLevelNone)).Methods("GET")
|
me.HandleFunc("/logout", handler.Web(viewLogout, UserLevelNone)).Methods("GET")
|
||||||
|
@ -109,6 +110,7 @@ func InitRoutes(apper Apper, r *mux.Router) *mux.Router {
|
||||||
apiMe.HandleFunc("/password", handler.All(updatePassphrase)).Methods("POST")
|
apiMe.HandleFunc("/password", handler.All(updatePassphrase)).Methods("POST")
|
||||||
apiMe.HandleFunc("/self", handler.All(updateSettings)).Methods("POST")
|
apiMe.HandleFunc("/self", handler.All(updateSettings)).Methods("POST")
|
||||||
apiMe.HandleFunc("/invites", handler.User(handleCreateUserInvite)).Methods("POST")
|
apiMe.HandleFunc("/invites", handler.User(handleCreateUserInvite)).Methods("POST")
|
||||||
|
apiMe.HandleFunc("/import", handler.User(handleImport)).Methods("POST")
|
||||||
|
|
||||||
// Sign up validation
|
// Sign up validation
|
||||||
write.HandleFunc("/api/alias", handler.All(handleUsernameCheck)).Methods("POST")
|
write.HandleFunc("/api/alias", handler.All(handleUsernameCheck)).Methods("POST")
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
{{define "import"}}
|
||||||
|
{{template "header" .}}
|
||||||
|
<style>
|
||||||
|
input[type=file] {
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.86em;
|
||||||
|
display: block;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="snug content-container">
|
||||||
|
<h1 id="import-header">Import posts</h1>
|
||||||
|
{{if .Message}}
|
||||||
|
<div class="alert {{if .InfoMsg}}info{{else}}success{{end}}">
|
||||||
|
<p>{{.Message}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .Flashes}}
|
||||||
|
<ul class="errors">
|
||||||
|
{{range .Flashes}}<li class="urgent">{{.}}</li>{{end}}
|
||||||
|
</ul>
|
||||||
|
{{end}}
|
||||||
|
<p>Publish plain text or Markdown files to your account by uploading them below.</p>
|
||||||
|
<div class="formContainer">
|
||||||
|
<form id="importPosts" class="prominent" enctype="multipart/form-data" action="/api/me/import" method="POST">
|
||||||
|
<label>Select some files to import:
|
||||||
|
<input id="fileInput" class="fileInput" name="files" type="file" multiple accept="text/markdown, text/plain"/>
|
||||||
|
</label>
|
||||||
|
<input id="fileDates" name="fileDates" hidden/>
|
||||||
|
<label>
|
||||||
|
Import these posts to:
|
||||||
|
<select name="collection">
|
||||||
|
{{range $i, $el := .Collections}}
|
||||||
|
<option value="{{.Alias}}" {{if eq $i 0}}selected{{end}}>{{.DisplayTitle}}</option>
|
||||||
|
{{end}}
|
||||||
|
<option value="">Drafts</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
<script>
|
||||||
|
const fileInput = document.getElementById('fileInput');
|
||||||
|
const fileDates = document.getElementById('fileDates');
|
||||||
|
fileInput.addEventListener('change', (e) => {
|
||||||
|
const files = e.target.files;
|
||||||
|
let dateMap = {};
|
||||||
|
for (let file of files) {
|
||||||
|
dateMap[file.name] = file.lastModified / 1000;
|
||||||
|
}
|
||||||
|
fileDates.value = JSON.stringify(dateMap);
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
<input type="submit" value="Import" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "footer" .}}
|
||||||
|
{{end}}
|
|
@ -10,6 +10,7 @@
|
||||||
<li class="separator"><hr /></li>
|
<li class="separator"><hr /></li>
|
||||||
{{if .IsAdmin}}<li><a href="/admin">Admin</a></li>{{end}}
|
{{if .IsAdmin}}<li><a href="/admin">Admin</a></li>{{end}}
|
||||||
<li><a href="/me/settings">Settings</a></li>
|
<li><a href="/me/settings">Settings</a></li>
|
||||||
|
<li><a href="/me/import">Import posts</a></li>
|
||||||
<li><a href="/me/export">Export</a></li>
|
<li><a href="/me/export">Export</a></li>
|
||||||
<li class="separator"><hr /></li>
|
<li class="separator"><hr /></li>
|
||||||
<li><a href="/me/logout">Log out</a></li>
|
<li><a href="/me/logout">Log out</a></li>
|
||||||
|
@ -32,6 +33,7 @@
|
||||||
<ul><li><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul>
|
<ul><li><a>{{.Username}}</a> <img class="ic-18dp" src="/img/ic_down_arrow_dark@2x.png" /><ul>
|
||||||
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}}
|
{{if .IsAdmin}}<li><a href="/admin">Admin dashboard</a></li>{{end}}
|
||||||
<li><a href="/me/settings">Account settings</a></li>
|
<li><a href="/me/settings">Account settings</a></li>
|
||||||
|
<li><a href="/me/import">Import posts</a></li>
|
||||||
<li><a href="/me/export">Export</a></li>
|
<li><a href="/me/export">Export</a></li>
|
||||||
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}}
|
{{if .CanInvite}}<li><a href="/me/invites">Invite people</a></li>{{end}}
|
||||||
<li class="separator"><hr /></li>
|
<li class="separator"><hr /></li>
|
||||||
|
|
|
@ -8,18 +8,7 @@
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
label {
|
table.classy {
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
font-size: 1em;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.5rem;
|
|
||||||
display: block;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
input, table.classy {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
table.classy.export a {
|
table.classy.export a {
|
||||||
|
@ -34,7 +23,7 @@ table td {
|
||||||
<h1>Invite people</h1>
|
<h1>Invite people</h1>
|
||||||
<p>Invite others to join <em>{{.SiteName}}</em> by generating and sharing invite links below.</p>
|
<p>Invite others to join <em>{{.SiteName}}</em> by generating and sharing invite links below.</p>
|
||||||
|
|
||||||
<form style="margin: 2em 0" action="/api/me/invites" method="post">
|
<form style="margin: 2em 0" class="prominent" action="/api/me/invites" method="post">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="half">
|
<div class="half">
|
||||||
<label for="uses">Maximum number of uses:</label>
|
<label for="uses">Maximum number of uses:</label>
|
||||||
|
|
Loading…
Reference in New Issue