mirror of
https://github.com/go-shiori/shiori.git
synced 2025-10-06 11:36:16 +08:00
resolving conflicts with master
This commit is contained in:
commit
f00fb47845
16 changed files with 386 additions and 75 deletions
|
@ -28,7 +28,7 @@ Shiori is a simple bookmarks manager written in Go language. Intended as a simpl
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
You can download the latest version of `shiori` from the release page, then put it in your `PATH`. If you want to build from source, make sure `go` is installed, then run :
|
You can download the latest version of `shiori` from [the release page](https://github.com/RadhiFadlillah/shiori/releases/latest), then put it in your `PATH`. If you want to build from source, make sure `go` is installed, then run :
|
||||||
|
|
||||||
```
|
```
|
||||||
go get github.com/RadhiFadlillah/shiori
|
go get github.com/RadhiFadlillah/shiori
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -2,6 +2,8 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -45,7 +47,7 @@ var (
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
keyword, _ := cmd.Flags().GetString("search")
|
keyword, _ := cmd.Flags().GetString("search")
|
||||||
err := printAccounts(keyword)
|
err := printAccounts(keyword, os.Stdout)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println(err)
|
cError.Println(err)
|
||||||
return
|
return
|
||||||
|
@ -103,7 +105,7 @@ func init() {
|
||||||
|
|
||||||
func addAccount(username, password string) error {
|
func addAccount(username, password string) error {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return fmt.Errorf("Username must not empty")
|
return fmt.Errorf("Username must not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(password) < 8 {
|
if len(password) < 8 {
|
||||||
|
@ -118,15 +120,15 @@ func addAccount(username, password string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func printAccounts(keyword string) error {
|
func printAccounts(keyword string, wr io.Writer) error {
|
||||||
accounts, err := DB.GetAccounts(keyword, false)
|
accounts, err := DB.GetAccounts(keyword, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, account := range accounts {
|
for _, account := range accounts {
|
||||||
cIndex.Print("- ")
|
cIndex.Fprint(wr, "- ")
|
||||||
fmt.Println(account.Username)
|
fmt.Fprintln(wr, account.Username)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
50
cmd/account_test.go
Normal file
50
cmd/account_test.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddAccount(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"", "", "Username must not be empty"},
|
||||||
|
{"abc", "abc", "Password must be at least"},
|
||||||
|
{"abc", "fooBar123", ""},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
err := addAccount(tt.username, tt.password)
|
||||||
|
if err != nil {
|
||||||
|
if tt.want == "" {
|
||||||
|
t.Errorf("got unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.want) {
|
||||||
|
t.Errorf("expected error containing '%s', got error '%v'", err, tt.want)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.want != "" {
|
||||||
|
t.Errorf("expected error '%s', got no error", tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintAccounts(t *testing.T) {
|
||||||
|
if err := addAccount("foo", "fooBar123"); err != nil {
|
||||||
|
t.Errorf("failed to add test account: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := bytes.NewBufferString("")
|
||||||
|
err := printAccounts("", b)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("got unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
got := b.String()
|
||||||
|
if !strings.Contains(got, "foo") {
|
||||||
|
t.Errorf("expected string containing 'foo', got '%s'", got)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"html/template"
|
|
||||||
nurl "net/url"
|
nurl "net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -81,7 +80,7 @@ func addBookmark(base model.Bookmark, offline bool) (book model.Bookmark, err er
|
||||||
book.MinReadTime = article.Meta.MinReadTime
|
book.MinReadTime = article.Meta.MinReadTime
|
||||||
book.MaxReadTime = article.Meta.MaxReadTime
|
book.MaxReadTime = article.Meta.MaxReadTime
|
||||||
book.Content = article.Content
|
book.Content = article.Content
|
||||||
book.HTML = template.HTML(article.RawContent)
|
book.HTML = article.RawContent
|
||||||
|
|
||||||
if book.Title == "" {
|
if book.Title == "" {
|
||||||
book.Title = article.Meta.Title
|
book.Title = article.Meta.Title
|
||||||
|
|
73
cmd/add_test.go
Normal file
73
cmd/add_test.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/RadhiFadlillah/shiori/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAddBookMark(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
bookmark model.Bookmark
|
||||||
|
offline bool
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
model.Bookmark{},
|
||||||
|
true, "URL must not be empty",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model.Bookmark{
|
||||||
|
URL: "https://github.com/RadhiFadlillah/shiori",
|
||||||
|
},
|
||||||
|
true, "Title must not be empty",
|
||||||
|
},
|
||||||
|
{model.Bookmark{URL: "foo", Title: "Foo"}, true, ""},
|
||||||
|
{
|
||||||
|
model.Bookmark{
|
||||||
|
URL: "https://github.com/RadhiFadlillah/shiori",
|
||||||
|
Title: "Shiori",
|
||||||
|
},
|
||||||
|
true, "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
model.Bookmark{
|
||||||
|
URL: "https://github.com/RadhiFadlillah/shiori/issues",
|
||||||
|
},
|
||||||
|
false, "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
bk, err := addBookmark(tt.bookmark, tt.offline)
|
||||||
|
if err != nil {
|
||||||
|
if tt.want == "" {
|
||||||
|
t.Errorf("got unexpected error: '%v'", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.want) {
|
||||||
|
t.Errorf("expected error '%s', got '%v'", tt.want, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.bookmark.URL == "" {
|
||||||
|
t.Errorf("expected error '%s', got '%v'", tt.want, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.offline && tt.bookmark.Title == "" {
|
||||||
|
t.Error("expected error 'Title must not be empty', got no error")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.want != "" {
|
||||||
|
t.Errorf("expected error '%s', got no error", tt.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.offline && bk.Title != tt.bookmark.Title {
|
||||||
|
t.Errorf("expected title '%s', got '%s'", tt.bookmark.Title, bk.Title)
|
||||||
|
}
|
||||||
|
if !tt.offline && bk.Title == "" {
|
||||||
|
t.Error("expected title, got empty string ''")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
28
cmd/cmd_test.go
Normal file
28
cmd/cmd_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
db "github.com/RadhiFadlillah/shiori/database"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
testDBFile := "shiori_test.db"
|
||||||
|
sqliteDB, err := db.OpenSQLiteDatabase(testDBFile)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to create tests DB: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
DB = sqliteDB
|
||||||
|
|
||||||
|
code := m.Run()
|
||||||
|
|
||||||
|
if err := os.Remove(testDBFile); err != nil {
|
||||||
|
fmt.Printf("failed to delete tests DB: %v", err)
|
||||||
|
}
|
||||||
|
os.Exit(code)
|
||||||
|
|
||||||
|
}
|
26
cmd/serve.go
26
cmd/serve.go
|
@ -41,8 +41,14 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare template
|
// Prepare template
|
||||||
|
funcMap := template.FuncMap{
|
||||||
|
"html": func(s string) template.HTML {
|
||||||
|
return template.HTML(s)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
tplFile, _ := assets.ReadFile("cache.html")
|
tplFile, _ := assets.ReadFile("cache.html")
|
||||||
tplCache, err = template.New("cache.html").Parse(string(tplFile))
|
tplCache, err = template.New("cache.html").Funcs(funcMap).Parse(string(tplFile))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println("Failed to generate HTML template")
|
cError.Println("Failed to generate HTML template")
|
||||||
return
|
return
|
||||||
|
@ -75,7 +81,13 @@ var (
|
||||||
port, _ := cmd.Flags().GetInt("port")
|
port, _ := cmd.Flags().GetInt("port")
|
||||||
url := fmt.Sprintf(":%d", port)
|
url := fmt.Sprintf(":%d", port)
|
||||||
logrus.Infoln("Serve shiori in", url)
|
logrus.Infoln("Serve shiori in", url)
|
||||||
logrus.Fatalln(http.ListenAndServe(url, router))
|
svr := &http.Server{
|
||||||
|
Addr: url,
|
||||||
|
Handler: router,
|
||||||
|
ReadTimeout: 10 * time.Second,
|
||||||
|
WriteTimeout: 20 * time.Second,
|
||||||
|
}
|
||||||
|
logrus.Fatalln(svr.ListenAndServe())
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -245,6 +257,10 @@ func apiInsertBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Pa
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiUpdateBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
func apiUpdateBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
// Get url parameter
|
||||||
|
_, dontOverwrite := r.URL.Query()["dont-overwrite"]
|
||||||
|
overwrite := !dontOverwrite
|
||||||
|
|
||||||
// Check token
|
// Check token
|
||||||
err := checkAPIToken(r)
|
err := checkAPIToken(r)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
@ -256,13 +272,9 @@ func apiUpdateBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Pa
|
||||||
|
|
||||||
// Convert tags and ID
|
// Convert tags and ID
|
||||||
id := []string{fmt.Sprintf("%d", request.ID)}
|
id := []string{fmt.Sprintf("%d", request.ID)}
|
||||||
tags := make([]string, len(request.Tags))
|
|
||||||
for i, tag := range request.Tags {
|
|
||||||
tags[i] = tag.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update bookmark
|
// Update bookmark
|
||||||
bookmarks, err := updateBookmarks(id, request.URL, request.Title, request.Excerpt, tags, false)
|
bookmarks, err := updateBookmarks(id, request, false, overwrite)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
// Return new saved result
|
// Return new saved result
|
||||||
|
|
|
@ -2,7 +2,6 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -31,9 +30,10 @@ var (
|
||||||
tags, _ := cmd.Flags().GetStringSlice("tags")
|
tags, _ := cmd.Flags().GetStringSlice("tags")
|
||||||
offline, _ := cmd.Flags().GetBool("offline")
|
offline, _ := cmd.Flags().GetBool("offline")
|
||||||
skipConfirmation, _ := cmd.Flags().GetBool("yes")
|
skipConfirmation, _ := cmd.Flags().GetBool("yes")
|
||||||
|
overwriteMetadata := !cmd.Flags().Changed("dont-overwrite")
|
||||||
|
|
||||||
// Check if --url flag is used
|
// Check if --url flag is used
|
||||||
if url != "" {
|
if cmd.Flags().Changed("url") {
|
||||||
if len(args) != 1 {
|
if len(args) != 1 {
|
||||||
cError.Println("Update only accepts one index while using --url flag")
|
cError.Println("Update only accepts one index while using --url flag")
|
||||||
return
|
return
|
||||||
|
@ -46,6 +46,11 @@ var (
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if --excerpt flag is used
|
||||||
|
if !cmd.Flags().Changed("excerpt") {
|
||||||
|
excerpt = "empty"
|
||||||
|
}
|
||||||
|
|
||||||
// If no arguments, confirm to user
|
// If no arguments, confirm to user
|
||||||
if len(args) == 0 && !skipConfirmation {
|
if len(args) == 0 && !skipConfirmation {
|
||||||
confirmUpdate := ""
|
confirmUpdate := ""
|
||||||
|
@ -59,7 +64,18 @@ var (
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update bookmarks
|
// Update bookmarks
|
||||||
bookmarks, err := updateBookmarks(args, url, title, excerpt, tags, offline)
|
base := model.Bookmark{
|
||||||
|
URL: url,
|
||||||
|
Title: title,
|
||||||
|
Excerpt: excerpt,
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Tags = make([]model.Tag, len(tags))
|
||||||
|
for i, tag := range tags {
|
||||||
|
base.Tags[i] = model.Tag{Name: tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
bookmarks, err := updateBookmarks(args, base, offline, overwriteMetadata)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println(err)
|
cError.Println(err)
|
||||||
return
|
return
|
||||||
|
@ -77,14 +93,15 @@ func init() {
|
||||||
updateCmd.Flags().StringSliceP("tags", "t", []string{}, "Comma-separated tags for this bookmark.")
|
updateCmd.Flags().StringSliceP("tags", "t", []string{}, "Comma-separated tags for this bookmark.")
|
||||||
updateCmd.Flags().BoolP("offline", "o", false, "Update bookmark without fetching data from internet.")
|
updateCmd.Flags().BoolP("offline", "o", false, "Update bookmark without fetching data from internet.")
|
||||||
updateCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and update ALL bookmarks")
|
updateCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and update ALL bookmarks")
|
||||||
|
updateCmd.Flags().Bool("dont-overwrite", false, "Don't overwrite existing metadata. Useful when only want to update bookmark's content.")
|
||||||
rootCmd.AddCommand(updateCmd)
|
rootCmd.AddCommand(updateCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateBookmarks(indices []string, url, title, excerpt string, tags []string, offline bool) ([]model.Bookmark, error) {
|
func updateBookmarks(indices []string, base model.Bookmark, offline, overwrite bool) ([]model.Bookmark, error) {
|
||||||
mutex := sync.Mutex{}
|
mutex := sync.Mutex{}
|
||||||
|
|
||||||
// Clear UTM parameters from URL
|
// Clear UTM parameters from URL
|
||||||
url, err := clearUTMParams(url)
|
url, err := clearUTMParams(base.URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []model.Bookmark{}, err
|
return []model.Bookmark{}, err
|
||||||
}
|
}
|
||||||
|
@ -114,14 +131,17 @@ func updateBookmarks(indices []string, url, title, excerpt string, tags []string
|
||||||
|
|
||||||
article, err := readability.Parse(book.URL, 10*time.Second)
|
article, err := readability.Parse(book.URL, 10*time.Second)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
if overwrite {
|
||||||
book.Title = article.Meta.Title
|
book.Title = article.Meta.Title
|
||||||
book.ImageURL = article.Meta.Image
|
|
||||||
book.Excerpt = article.Meta.Excerpt
|
book.Excerpt = article.Meta.Excerpt
|
||||||
|
}
|
||||||
|
|
||||||
|
book.ImageURL = article.Meta.Image
|
||||||
book.Author = article.Meta.Author
|
book.Author = article.Meta.Author
|
||||||
book.MinReadTime = article.Meta.MinReadTime
|
book.MinReadTime = article.Meta.MinReadTime
|
||||||
book.MaxReadTime = article.Meta.MaxReadTime
|
book.MaxReadTime = article.Meta.MaxReadTime
|
||||||
book.Content = article.Content
|
book.Content = article.Content
|
||||||
book.HTML = template.HTML(article.RawContent)
|
book.HTML = article.RawContent
|
||||||
|
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
bookmarks[pos] = book
|
bookmarks[pos] = book
|
||||||
|
@ -136,26 +156,26 @@ func updateBookmarks(indices []string, url, title, excerpt string, tags []string
|
||||||
// Map the tags to be deleted
|
// Map the tags to be deleted
|
||||||
addedTags := make(map[string]struct{})
|
addedTags := make(map[string]struct{})
|
||||||
deletedTags := make(map[string]struct{})
|
deletedTags := make(map[string]struct{})
|
||||||
for _, tag := range tags {
|
for _, tag := range base.Tags {
|
||||||
tag = strings.ToLower(tag)
|
tagName := strings.ToLower(tag.Name)
|
||||||
tag = strings.TrimSpace(tag)
|
tagName = strings.TrimSpace(tagName)
|
||||||
|
|
||||||
if strings.HasPrefix(tag, "-") {
|
if strings.HasPrefix(tagName, "-") {
|
||||||
tag = strings.TrimPrefix(tag, "-")
|
tagName = strings.TrimPrefix(tagName, "-")
|
||||||
deletedTags[tag] = struct{}{}
|
deletedTags[tagName] = struct{}{}
|
||||||
} else {
|
} else {
|
||||||
addedTags[tag] = struct{}{}
|
addedTags[tagName] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default title, excerpt and tags
|
// Set default title, excerpt and tags
|
||||||
for i := range bookmarks {
|
for i := range bookmarks {
|
||||||
if title != "" {
|
if base.Title != "" && overwrite {
|
||||||
bookmarks[i].Title = title
|
bookmarks[i].Title = base.Title
|
||||||
}
|
}
|
||||||
|
|
||||||
if excerpt != "" {
|
if base.Excerpt != "empty" && overwrite {
|
||||||
bookmarks[i].Excerpt = excerpt
|
bookmarks[i].Excerpt = base.Excerpt
|
||||||
}
|
}
|
||||||
|
|
||||||
tempAddedTags := make(map[string]struct{})
|
tempAddedTags := make(map[string]struct{})
|
||||||
|
|
102
cmd/update_test.go
Normal file
102
cmd/update_test.go
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/RadhiFadlillah/shiori/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUpdateBookMark(t *testing.T) {
|
||||||
|
testbks := []model.Bookmark{
|
||||||
|
{
|
||||||
|
URL: "https://github.com/RadhiFadlillah/shiori/releases",
|
||||||
|
Title: "Releases",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
URL: "https://github.com/RadhiFadlillah/shiori/projects",
|
||||||
|
Title: "Projects",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, tb := range testbks {
|
||||||
|
bk, err := addBookmark(tb, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create testing bookmarks: %v", err)
|
||||||
|
}
|
||||||
|
testbks[i].ID = bk.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
indices []string
|
||||||
|
url string
|
||||||
|
title string
|
||||||
|
excerpt string
|
||||||
|
tags []string
|
||||||
|
offline bool
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
indices: []string{"9000"},
|
||||||
|
want: "No matching index found",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
indices: []string{"-1"},
|
||||||
|
want: "Index is not valid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
indices: []string{"3", "-1"},
|
||||||
|
want: "Index is not valid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
indices: []string{fmt.Sprintf("%d", testbks[0].ID)},
|
||||||
|
url: testbks[0].URL,
|
||||||
|
title: testbks[0].Title + " updated",
|
||||||
|
offline: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
indices: []string{fmt.Sprintf("%d", testbks[0].ID)},
|
||||||
|
offline: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
indices: []string{fmt.Sprintf("%d", testbks[1].ID)},
|
||||||
|
offline: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
base := model.Bookmark{
|
||||||
|
URL: tt.url,
|
||||||
|
Title: tt.title,
|
||||||
|
Excerpt: tt.excerpt,
|
||||||
|
}
|
||||||
|
|
||||||
|
base.Tags = make([]model.Tag, len(tt.tags))
|
||||||
|
for i, tag := range tt.tags {
|
||||||
|
base.Tags[i] = model.Tag{Name: tag}
|
||||||
|
}
|
||||||
|
|
||||||
|
bks, err := updateBookmarks(tt.indices, base, tt.offline, true)
|
||||||
|
if err != nil {
|
||||||
|
if tt.want == "" {
|
||||||
|
t.Errorf("got unexpected error: '%v'", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), tt.want) {
|
||||||
|
t.Errorf("expected error '%s', got '%v'", tt.want, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.want != "" {
|
||||||
|
t.Errorf("expected error '%s', got no errors", tt.want)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(bks) == 0 {
|
||||||
|
t.Error("expected at least 1 bookmark, got 0")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bk := bks[0]
|
||||||
|
if tt.title == "" && bk.Title == tt.title {
|
||||||
|
t.Errorf("expected title as '%s', got '%s'", tt.title, bk.Title)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -81,11 +81,11 @@ func OpenSQLiteDatabase(databasePath string) (*SQLiteDatabase, error) {
|
||||||
func (db *SQLiteDatabase) CreateBookmark(bookmark model.Bookmark) (bookmarkID int64, err error) {
|
func (db *SQLiteDatabase) CreateBookmark(bookmark model.Bookmark) (bookmarkID int64, err error) {
|
||||||
// Check URL and title
|
// Check URL and title
|
||||||
if bookmark.URL == "" {
|
if bookmark.URL == "" {
|
||||||
return -1, fmt.Errorf("URL must not empty")
|
return -1, fmt.Errorf("URL must not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bookmark.Title == "" {
|
if bookmark.Title == "" {
|
||||||
return -1, fmt.Errorf("Title must not empty")
|
return -1, fmt.Errorf("Title must not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if bookmark.Modified == "" {
|
if bookmark.Modified == "" {
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import "html/template"
|
|
||||||
|
|
||||||
// Tag is tag for the bookmark
|
// Tag is tag for the bookmark
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
ID int64 `db:"id" json:"id"`
|
ID int64 `db:"id" json:"id"`
|
||||||
|
@ -22,7 +20,7 @@ type Bookmark struct {
|
||||||
MaxReadTime int `db:"max_read_time" json:"maxReadTime"`
|
MaxReadTime int `db:"max_read_time" json:"maxReadTime"`
|
||||||
Modified string `db:"modified" json:"modified"`
|
Modified string `db:"modified" json:"modified"`
|
||||||
Content string `db:"content" json:"-"`
|
Content string `db:"content" json:"-"`
|
||||||
HTML template.HTML `db:"html" json:"-"`
|
HTML string `db:"html" json:"-"`
|
||||||
Tags []Tag `json:"tags"`
|
Tags []Tag `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
{{end}} {{end}}
|
{{end}} {{end}}
|
||||||
</div>
|
</div>
|
||||||
<div id="content">
|
<div id="content">
|
||||||
{{.HTML}}
|
{{html .HTML}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -96,7 +96,7 @@
|
||||||
</div>
|
</div>
|
||||||
<template v-if="!displayTags">
|
<template v-if="!displayTags">
|
||||||
<div id="grid">
|
<div id="grid">
|
||||||
<div v-for="column in gridColumns" class="column">
|
<div v-for="column in gridColumns" class="column" :style="{maxWidth: columnWidth}">
|
||||||
<div v-for="item in column" class="bookmark" :class="{checked: isBookmarkChecked(item.index)}" :ref="'bookmark-'+item.index">
|
<div v-for="item in column" class="bookmark" :class="{checked: isBookmarkChecked(item.index)}" :ref="'bookmark-'+item.index">
|
||||||
<a class="checkbox" @click="toggleBookmarkCheck(item.index)">
|
<a class="checkbox" @click="toggleBookmarkCheck(item.index)">
|
||||||
<i class="fas fa-check"></i>
|
<i class="fas fa-check"></i>
|
||||||
|
@ -404,19 +404,14 @@
|
||||||
|
|
||||||
},
|
},
|
||||||
updateBookmark: function (idx) {
|
updateBookmark: function (idx) {
|
||||||
var bookmark = this.bookmarks[idx];
|
var bookmark = this.bookmarks[idx],
|
||||||
|
sendUpdateRequest = function (overwrite) {
|
||||||
|
var url = "/api/bookmarks";
|
||||||
|
if (!overwrite) url += "?dont-overwrite";
|
||||||
|
|
||||||
this.dialog.visible = true;
|
instance.put(url, {
|
||||||
this.dialog.isError = false;
|
|
||||||
this.dialog.loading = false;
|
|
||||||
this.dialog.title = "Update Bookmark";
|
|
||||||
this.dialog.content = "Update data of <b>\"" + bookmark.title.trim() + "\"</b> ? This action is irreversible.";
|
|
||||||
this.dialog.mainChoice = "Yes";
|
|
||||||
this.dialog.secondChoice = "No";
|
|
||||||
this.dialog.mainAction = function () {
|
|
||||||
app.dialog.loading = true;
|
|
||||||
instance.put('/api/bookmarks', {
|
|
||||||
id: bookmark.id,
|
id: bookmark.id,
|
||||||
|
excerpt: overwrite ? "empty" : bookmark.excerpt
|
||||||
}, {
|
}, {
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
})
|
})
|
||||||
|
@ -431,6 +426,28 @@
|
||||||
app.showDialogError("Error Updating Bookmark", errorMsg.trim());
|
app.showDialogError("Error Updating Bookmark", errorMsg.trim());
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.dialog.visible = true;
|
||||||
|
this.dialog.isError = false;
|
||||||
|
this.dialog.loading = false;
|
||||||
|
this.dialog.title = "Update Bookmark";
|
||||||
|
this.dialog.content = "Update data of <b>\"" + bookmark.title.trim() + "\"</b> ? This action is irreversible.";
|
||||||
|
this.dialog.mainChoice = "Yes";
|
||||||
|
this.dialog.secondChoice = "No";
|
||||||
|
this.dialog.mainAction = function () {
|
||||||
|
app.dialog.title = "Overwrite Metadata";
|
||||||
|
app.dialog.content = "Overwrite the existing bookmark's metadata ?";
|
||||||
|
app.dialog.mainChoice = "Yes";
|
||||||
|
app.dialog.secondChoice = "No";
|
||||||
|
app.dialog.mainAction = function () {
|
||||||
|
app.dialog.loading = true;
|
||||||
|
sendUpdateRequest(true);
|
||||||
|
};
|
||||||
|
app.dialog.secondAction = function () {
|
||||||
|
app.dialog.loading = true;
|
||||||
|
sendUpdateRequest(false);
|
||||||
|
};
|
||||||
|
};
|
||||||
this.dialog.secondAction = function () {
|
this.dialog.secondAction = function () {
|
||||||
app.dialog.visible = false;
|
app.dialog.visible = false;
|
||||||
app.$nextTick(function () {
|
app.$nextTick(function () {
|
||||||
|
@ -585,6 +602,12 @@
|
||||||
|
|
||||||
return finalContent;
|
return finalContent;
|
||||||
},
|
},
|
||||||
|
columnWidth: function () {
|
||||||
|
var nColumn = Math.round(this.windowWidth / 500),
|
||||||
|
percent = Math.round(100 / nColumn * 1000) / 1000;
|
||||||
|
|
||||||
|
return percent + "%";
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
'inputBookmark.url': function (newURL) {
|
'inputBookmark.url': function (newURL) {
|
||||||
|
|
|
@ -538,6 +538,8 @@
|
||||||
color: @fontColor;
|
color: @fontColor;
|
||||||
font-size: 1.3em;
|
font-size: 1.3em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.bookmark-url {
|
.bookmark-url {
|
||||||
.bookmark-time;
|
.bookmark-time;
|
||||||
|
@ -586,6 +588,8 @@
|
||||||
.bookmark-excerpt {
|
.bookmark-excerpt {
|
||||||
padding: 16px 16px 0;
|
padding: 16px 16px 0;
|
||||||
color: @fontColor;
|
color: @fontColor;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.bookmark-tags {
|
.bookmark-tags {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
Loading…
Add table
Reference in a new issue