Add edit function in web view

This commit is contained in:
Radhi Fadlillah 2018-02-13 16:14:08 +07:00
parent 59208fb377
commit 5a2fed1888
6 changed files with 221 additions and 135 deletions

View file

@ -88,5 +88,6 @@ func addBookmark(url, title, excerpt string, tags []string, offline bool) (book
return book, err
}
bookmark.Modified = time.Now().UTC().Format("2006-01-02 15:04:05")
return bookmark, nil
}

View file

@ -28,6 +28,7 @@ var (
router.GET("/webfonts/*filepath", serveFiles)
router.GET("/api/bookmarks", apiGetBookmarks)
router.POST("/api/bookmarks", apiInsertBookmarks)
router.PUT("/api/bookmarks", apiUpdateBookmarks)
// Route for panic
router.PanicHandler = func(w http.ResponseWriter, r *http.Request, arg interface{}) {
@ -80,3 +81,25 @@ func apiInsertBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Pa
err = json.NewEncoder(w).Encode(&book)
checkError(err)
}
func apiUpdateBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// Decode request
request := model.Bookmark{}
err := json.NewDecoder(r.Body).Decode(&request)
checkError(err)
// Convert tags and 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
bookmarks, err := updateBookmarks(id, request.URL, request.Title, request.Excerpt, tags, false)
checkError(err)
// Return new saved result
err = json.NewEncoder(w).Encode(&bookmarks[0])
checkError(err)
}

View file

@ -58,109 +58,13 @@ var (
}
}
// Read bookmarks from database
bookmarks, err := DB.GetBookmarks(db.GetBookmarksOptions{WithContents: true}, args...)
// Update bookmarks
bookmarks, err := updateBookmarks(args, url, title, excerpt, tags, offline)
if err != nil {
cError.Println(err)
return
}
if len(bookmarks) == 0 {
cError.Println("No matching index found")
return
}
if url != "" && len(bookmarks) == 1 {
bookmarks[0].URL = url
}
// If not offline, fetch articles from internet
if !offline {
mutex := sync.Mutex{}
waitGroup := sync.WaitGroup{}
for i, book := range bookmarks {
go func(pos int, book model.Bookmark) {
waitGroup.Add(1)
defer waitGroup.Done()
article, err := readability.Parse(book.URL, 10*time.Second)
if err == nil {
book.Title = article.Meta.Title
book.ImageURL = article.Meta.Image
book.Excerpt = article.Meta.Excerpt
book.Author = article.Meta.Author
book.MinReadTime = article.Meta.MinReadTime
book.MaxReadTime = article.Meta.MaxReadTime
book.Content = article.Content
book.HTML = article.RawContent
mutex.Lock()
bookmarks[pos] = book
mutex.Unlock()
}
}(i, book)
}
waitGroup.Wait()
}
// Map the tags to be deleted
addedTags := make(map[string]struct{})
deletedTags := make(map[string]struct{})
for _, tag := range tags {
tag = strings.ToLower(tag)
tag = strings.TrimSpace(tag)
if strings.HasPrefix(tag, "-") {
tag = strings.TrimPrefix(tag, "-")
deletedTags[tag] = struct{}{}
} else {
addedTags[tag] = struct{}{}
}
}
// Set default title, excerpt and tags
for i := range bookmarks {
if title != "" {
bookmarks[i].Title = title
}
if excerpt != "" {
bookmarks[i].Excerpt = excerpt
}
tempAddedTags := make(map[string]struct{})
for key, value := range addedTags {
tempAddedTags[key] = value
}
newTags := []model.Tag{}
for _, tag := range bookmarks[i].Tags {
if _, isDeleted := deletedTags[tag.Name]; isDeleted {
tag.Deleted = true
}
if _, alreadyExist := addedTags[tag.Name]; alreadyExist {
delete(tempAddedTags, tag.Name)
}
newTags = append(newTags, tag)
}
for tag := range tempAddedTags {
newTags = append(newTags, model.Tag{Name: tag})
}
bookmarks[i].Tags = newTags
}
err = DB.UpdateBookmarks(bookmarks)
if err != nil {
cError.Println("Failed to update bookmarks:", err)
return
}
printBookmark(bookmarks...)
},
}
@ -175,3 +79,107 @@ func init() {
updateCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt and update ALL bookmarks")
rootCmd.AddCommand(updateCmd)
}
func updateBookmarks(indices []string, url, title, excerpt string, tags []string, offline bool) ([]model.Bookmark, error) {
// Read bookmarks from database
bookmarks, err := DB.GetBookmarks(db.GetBookmarksOptions{WithContents: true}, indices...)
if err != nil {
return []model.Bookmark{}, err
}
if len(bookmarks) == 0 {
return []model.Bookmark{}, fmt.Errorf("No matching index found")
}
if url != "" && len(bookmarks) == 1 {
bookmarks[0].URL = url
}
// If not offline, fetch articles from internet
if !offline {
mutex := sync.Mutex{}
waitGroup := sync.WaitGroup{}
for i, book := range bookmarks {
go func(pos int, book model.Bookmark) {
waitGroup.Add(1)
defer waitGroup.Done()
article, err := readability.Parse(book.URL, 10*time.Second)
if err == nil {
book.Title = article.Meta.Title
book.ImageURL = article.Meta.Image
book.Excerpt = article.Meta.Excerpt
book.Author = article.Meta.Author
book.MinReadTime = article.Meta.MinReadTime
book.MaxReadTime = article.Meta.MaxReadTime
book.Content = article.Content
book.HTML = article.RawContent
mutex.Lock()
bookmarks[pos] = book
mutex.Unlock()
}
}(i, book)
}
waitGroup.Wait()
}
// Map the tags to be deleted
addedTags := make(map[string]struct{})
deletedTags := make(map[string]struct{})
for _, tag := range tags {
tag = strings.ToLower(tag)
tag = strings.TrimSpace(tag)
if strings.HasPrefix(tag, "-") {
tag = strings.TrimPrefix(tag, "-")
deletedTags[tag] = struct{}{}
} else {
addedTags[tag] = struct{}{}
}
}
// Set default title, excerpt and tags
for i := range bookmarks {
if title != "" {
bookmarks[i].Title = title
}
if excerpt != "" {
bookmarks[i].Excerpt = excerpt
}
tempAddedTags := make(map[string]struct{})
for key, value := range addedTags {
tempAddedTags[key] = value
}
newTags := []model.Tag{}
for _, tag := range bookmarks[i].Tags {
if _, isDeleted := deletedTags[tag.Name]; isDeleted {
tag.Deleted = true
}
if _, alreadyExist := addedTags[tag.Name]; alreadyExist {
delete(tempAddedTags, tag.Name)
}
newTags = append(newTags, tag)
}
for tag := range tempAddedTags {
newTags = append(newTags, model.Tag{Name: tag})
}
bookmarks[i].Tags = newTags
}
err = DB.UpdateBookmarks(bookmarks)
if err != nil {
return []model.Bookmark{}, fmt.Errorf("Failed to update bookmarks: %v", err)
}
return bookmarks, nil
}

File diff suppressed because one or more lines are too long

View file

@ -26,19 +26,20 @@
</div>
</div>
<div id="main">
<div id="new-bookmark">
<input type="text" v-model="newBookmark.url" placeholder="URL for the bookmark">
<template v-if="newBookmark.url !== ''">
<input type="text" v-model="newBookmark.title" placeholder="Custom bookmark title (optional)">
<input type="text" v-model="newBookmark.tags" placeholder="Space separated tags for this bookmark (optional)">
<textarea name="excerpt" v-model="newBookmark.excerpt" placeholder="Excerpt for this bookmark (optional)"></textarea>
<div id="input-bookmark">
<p v-if="inputBookmark.url !== ''">{{inputBookmark.id === -1 ? 'New bookmark' : 'Edit bookmark'}}</p>
<input type="text" ref="inputURL" v-model="inputBookmark.url" placeholder="URL for the bookmark">
<template v-if="inputBookmark.url !== ''">
<input type="text" v-model="inputBookmark.title" placeholder="Custom bookmark title (optional)">
<input type="text" v-model="inputBookmark.tags" placeholder="Space separated tags for this bookmark (optional)">
<textarea name="excerpt" v-model="inputBookmark.excerpt" placeholder="Excerpt for this bookmark (optional)"></textarea>
<div class="button-area">
<div class="spacer"></div>
<a v-if="newBookmark.loading">
<a v-if="inputBookmark.loading">
<i class="fas fa-fw fa-spinner fa-spin"></i>
</a>
<template v-else>
<a class="button">Cancel</a>
<a class="button" @click="clearInputBookmark">Cancel</a>
<a class="button" @click="saveBookmark">Done</a>
</template>
</div>
@ -46,7 +47,7 @@
</div>
<div id="grid">
<div v-for="column in gridColumns" class="column">
<div v-for="item in column" class="bookmark">
<div v-for="item in column" class="bookmark" :ref="'bookmark-'+item.index">
<a href="#" class="checkbox">
<i class="fas fa-check"></i>
</a>
@ -60,7 +61,7 @@
<a v-for="tag in item.tags" href="#">{{tag.name}}</a>
</div>
<div class="bookmark-menu">
<a href="#">
<a href="#" @click="editBookmark(item.index)">
<i class="fas fa-pencil-alt"></i>
</a>
<a href="#">
@ -89,7 +90,9 @@
windowWidth: 0,
loading: true,
bookmarks: [],
newBookmark: {
inputBookmark: {
index: -1,
id: -1,
url: "",
title: "",
tags: "",
@ -111,12 +114,12 @@
});
},
saveBookmark: function () {
if (this.newBookmark.loading) return;
this.newBookmark.loading = true;
if (this.inputBookmark.loading) return;
this.inputBookmark.loading = true;
if (this.newBookmark.url === "") return;
if (this.inputBookmark.url === "") return;
var tags = this.newBookmark.tags.replace(/\s+/g, ""),
var tags = this.inputBookmark.tags.replace(/\s+/g, " "),
listTag = tags === "" ? [] : listTag = tags.split(/\s+/g),
finalTag = [];
@ -126,29 +129,64 @@
});
}
instance.post('/api/bookmarks', {
url: this.newBookmark.url,
title: this.newBookmark.title,
excerpt: this.newBookmark.excerpt,
tags: finalTag
}, {
timeout: 15000
instance.request({
method: this.inputBookmark.id === -1 ? 'post' : 'put',
url: '/api/bookmarks',
timeout: 15000,
data: {
id: this.inputBookmark.id,
url: this.inputBookmark.url,
title: this.inputBookmark.title,
excerpt: this.inputBookmark.excerpt,
tags: finalTag
}
})
.then(function (response) {
app.clearNewBookmark();
app.bookmarks.unshift(response.data);
var idx = app.inputBookmark.index;
if (idx === -1) app.bookmarks.unshift(response.data);
else app.bookmarks.splice(idx, 1, response.data);
app.clearInputBookmark();
})
.catch(function (error) {
app.clearNewBookmark();
console.log(error);
});
},
clearNewBookmark: function () {
this.newBookmark.url = "";
this.newBookmark.title = "";
this.newBookmark.tags = "";
this.newBookmark.excerpt = "";
this.newBookmark.loading = false;
clearInputBookmark: function () {
var idx = this.inputBookmark.index;
this.inputBookmark.index = -1;
this.inputBookmark.id = -1;
this.inputBookmark.url = "";
this.inputBookmark.title = "";
this.inputBookmark.tags = "";
this.inputBookmark.excerpt = "";
this.inputBookmark.loading = false;
if (idx !== -1) app.$nextTick(function () {
var bookmarkItem = app.$refs['bookmark-' + idx];
bookmarkItem[0].scrollIntoView();
})
},
editBookmark: function (idx) {
var bookmark = this.bookmarks[idx],
tags = [];
for (var i = 0; i < bookmark.tags.length; i++) {
tags.push(bookmark.tags[i].name);
}
this.inputBookmark.index = idx;
this.inputBookmark.id = bookmark.id;
this.inputBookmark.url = bookmark.url;
this.inputBookmark.title = bookmark.title;
this.inputBookmark.tags = tags.join(" ");
this.inputBookmark.excerpt = bookmark.excerpt;
this.$nextTick(function () {
app.$refs.inputURL.focus();
});
},
bookmarkTime: function (book) {
var time = book.modified,
@ -199,6 +237,7 @@
for (var i = 0; i < this.bookmarks.length; i++) {
var bookmark = this.bookmarks[i];
bookmark.index = i;
finalContent[currentColumn].push(bookmark);
currentColumn += 1;
@ -208,6 +247,14 @@
return finalContent;
}
},
watch: {
'inputBookmark.url': function (newURL) {
if (newURL === "") this.clearInputBookmark();
else this.$nextTick(function () {
app.$refs.inputURL.focus();
});
}
},
mounted: function () {
this.windowWidth = window.innerWidth;
window.addEventListener('resize', function () {

View file

@ -80,7 +80,7 @@
margin-top: @headerHeight;
display: flex;
flex-flow: column nowrap;
#new-bookmark {
#input-bookmark {
align-self: center;
max-width: 600px;
width: 100%;
@ -89,6 +89,12 @@
margin: 32px 8px 20px;
background-color: @headerInputBg;
outline: 1px solid @border;
>p {
color: @fontColor;
font-weight: 600;
text-transform: uppercase;
padding: 16px;
}
input[type=text],
textarea {
outline: 1px solid @border;
@ -177,6 +183,7 @@
padding: 16px;
display: flex;
flex-flow: column nowrap;
border-bottom: 1px solid @border;
.bookmark-time {
color: @fontLightColor;
font-size: 0.9em;
@ -232,15 +239,14 @@
}
}
.bookmark-excerpt {
padding: 16px;
padding: 16px 16px 0;
color: @fontColor;
border-top: 1px solid @border;
}
.bookmark-tags {
display: flex;
flex-flow: row wrap;
padding: 0 12px 12px;
margin-top: -4px;
padding: 12px 12px 0;
margin-bottom: -4px;
a {
font-size: 0.9em;
padding: 4px;
@ -258,6 +264,7 @@
flex-flow: row nowrap;
border-top: 1px solid @border;
visibility: hidden;
margin-top: 16px;
a {
display: block;
flex: 1 0;