Implement update button in UI

This commit is contained in:
Radhi Fadlillah 2018-05-19 15:28:17 +07:00
parent 9048fe4bdc
commit fec02adfc2
6 changed files with 87 additions and 75 deletions

View file

@ -182,98 +182,48 @@ func (h *webHandler) apiUpdateBookmark(w http.ResponseWriter, r *http.Request, p
err := h.checkAPIToken(r)
checkError(err)
// Get url queries
_, dontOverwrite := r.URL.Query()["dont-overwrite"]
// Decode request
request := model.Bookmark{}
err = json.NewDecoder(r.Body).Decode(&request)
checkError(err)
// Make sure URL valid
parsedURL, err := nurl.ParseRequestURI(request.URL)
if err != nil || parsedURL.Host == "" {
panic(fmt.Errorf("URL is not valid"))
// Validate input
if request.Title != "" {
panic(fmt.Errorf("Title must not empty"))
}
clearUTMParams(parsedURL)
// Get existing bookmark from database
bookmarks, err := h.db.GetBookmarks(true, fmt.Sprintf("%d", request.ID))
checkError(err)
if len(bookmarks) == 0 {
panic(fmt.Errorf("No bookmark with matching index"))
}
// Set new bookmark data
book := bookmarks[0]
book.URL = parsedURL.String()
book.Title = request.Title
book.Excerpt = request.Excerpt
// Fetch data from internet
article, err := readability.Parse(parsedURL, 10*time.Second)
checkError(err)
book.ImageURL = article.Meta.Image
book.Author = article.Meta.Author
book.MinReadTime = article.Meta.MinReadTime
book.MaxReadTime = article.Meta.MaxReadTime
book.Content = article.Content
book.HTML = article.RawContent
if !dontOverwrite {
book.Title = article.Meta.Title
book.Excerpt = article.Meta.Excerpt
// Set new tags
for i := range book.Tags {
book.Tags[i].Deleted = true
}
// Check if user submit his own title or excerpt
if request.Title != "" {
book.Title = request.Title
}
if request.Excerpt != "" {
book.Excerpt = request.Excerpt
}
// Make sure title is not empty
if book.Title == "" {
book.Title = "Untitled"
}
// Create new tags from request
addedTags := make(map[string]struct{})
deletedTags := make(map[string]struct{})
for _, tag := range request.Tags {
tagName := strings.ToLower(tag.Name)
tagName = strings.TrimSpace(tagName)
if strings.HasPrefix(tagName, "-") {
tagName = strings.TrimPrefix(tagName, "-")
deletedTags[tagName] = struct{}{}
} else {
addedTags[tagName] = struct{}{}
}
}
newTags := []model.Tag{}
for _, tag := range book.Tags {
if _, isDeleted := deletedTags[tag.Name]; isDeleted {
tag.Deleted = true
for _, newTag := range request.Tags {
for i, oldTag := range book.Tags {
if newTag.Name == oldTag.Name {
newTag.ID = oldTag.ID
book.Tags[i].Deleted = false
break
}
}
if _, alreadyExist := addedTags[tag.Name]; alreadyExist {
delete(addedTags, tag.Name)
if newTag.ID == 0 {
book.Tags = append(book.Tags, newTag)
}
newTags = append(newTags, tag)
}
for tag := range addedTags {
newTags = append(newTags, model.Tag{Name: tag})
}
book.Tags = newTags
// Update database
book.Modified = time.Now().UTC().Format("2006-01-02 15:04:05")
res, err := h.db.UpdateBookmarks(book)
checkError(err)

View file

@ -408,6 +408,7 @@ func (db *SQLiteDatabase) UpdateBookmarks(bookmarks ...model.Bookmark) (result [
result = []model.Bookmark{}
for _, book := range bookmarks {
// Save bookmark
stmtUpdateBookmark.MustExec(
book.URL,
book.Title,
@ -419,12 +420,14 @@ func (db *SQLiteDatabase) UpdateBookmarks(bookmarks ...model.Bookmark) (result [
book.Modified,
book.ID)
// Save bookmark content
stmtUpdateBookmarkContent.MustExec(
book.Title,
book.Content,
book.HTML,
book.ID)
// Save bookmark tags
newTags := []model.Tag{}
for _, tag := range book.Tags {
if tag.Deleted {

View file

@ -1 +1 @@
.header-link{border-right:1px solid #E5E5E5;color:#232323;cursor:pointer;font-size:.9em;line-height:60px;overflow:hidden;padding:0 16px}.header-link:hover{color:#F44336}.full-overlay{position:fixed;z-index:101;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column;background-color:rgba(0,0,0,0.5);top:0;left:0;right:0;bottom:0;overflow:hidden;-webkit-box-pack:center;justify-content:center;padding:32px}.yla-dialog__overlay{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;-webkit-box-align:center;align-items:center;-webkit-box-pack:center;justify-content:center;min-width:0;min-height:0;overflow:hidden;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:10001;background-color:rgba(0,0,0,0.6);padding:32px}.yla-dialog__overlay .yla-dialog{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;min-width:400px;min-height:0;max-height:100%;overflow:hidden;background-color:#FFF;font-size:16px}.yla-dialog__overlay .yla-dialog>.yla-dialog__header{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:16px;min-height:0;background-color:#353535;color:#EEE;flex-shrink:0}.yla-dialog__overlay .yla-dialog>.yla-dialog__header>p{-webkit-box-flex:1;flex:1 0;font-weight:600;font-size:1em;text-transform:uppercase}.yla-dialog__overlay .yla-dialog>.yla-dialog__header>a:hover{color:#F44336}.yla-dialog__overlay .yla-dialog>.yla-dialog__body{padding:16px;display:grid;max-height:100%;min-height:80px;min-width:0;overflow:auto;grid-template-columns:max-content 1fr;-webkit-box-align:baseline;align-items:baseline;grid-gap:16px}.yla-dialog__overlay .yla-dialog>.yla-dialog__body>.yla-dialog__content{grid-column-start:1;grid-column-end:3;align-self:baseline;font-size:.9em}.yla-dialog__overlay .yla-dialog>.yla-dialog__body>input,.yla-dialog__overlay .yla-dialog>.yla-dialog__body>textarea{color:#232323;padding:8px;border:1px solid #E5E5E5;font-size:.9em;min-height:37px}.yla-dialog__overlay .yla-dialog>.yla-dialog__body>.suggestion{position:absolute;display:block;padding:8px;background-color:#EEE;border:1px solid #E5E5E5}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer{padding:16px;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;-webkit-box-pack:end;justify-content:flex-end;border-top:1px solid #E5E5E5}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer>a{text-transform:uppercase;padding:0 8px;font-size:.9em;font-weight:600;border-bottom:1px dashed transparent}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer>a:hover{color:#F44336}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer>a:focus{outline:none;color:#F44336;border-bottom:1px dashed #F44336}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer>i{width:19px;line-height:19px;text-align:center}
.header-link{border-right:1px solid #E5E5E5;color:#232323;cursor:pointer;font-size:.9em;line-height:60px;overflow:hidden;padding:0 16px}.header-link:hover{color:#F44336}.full-overlay{position:fixed;z-index:101;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column;background-color:rgba(0,0,0,0.5);top:0;left:0;right:0;bottom:0;overflow:hidden;-webkit-box-pack:center;justify-content:center;padding:32px}.yla-dialog__overlay{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;-webkit-box-align:center;align-items:center;-webkit-box-pack:center;justify-content:center;min-width:0;min-height:0;overflow:hidden;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:10001;background-color:rgba(0,0,0,0.6);padding:32px}.yla-dialog__overlay .yla-dialog{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;min-width:400px;min-height:0;max-height:100%;overflow:hidden;background-color:#FFF;font-size:16px}.yla-dialog__overlay .yla-dialog>.yla-dialog__header{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:16px;min-height:0;background-color:#353535;color:#EEE;flex-shrink:0}.yla-dialog__overlay .yla-dialog>.yla-dialog__header>p{-webkit-box-flex:1;flex:1 0;font-weight:600;font-size:1em;text-transform:uppercase}.yla-dialog__overlay .yla-dialog>.yla-dialog__header>a:hover{color:#F44336}.yla-dialog__overlay .yla-dialog>.yla-dialog__body{padding:16px;display:grid;max-height:100%;min-height:80px;min-width:0;overflow:auto;grid-template-columns:max-content 1fr;-webkit-box-align:baseline;align-items:baseline;grid-gap:16px}.yla-dialog__overlay .yla-dialog>.yla-dialog__body>p.yla-dialog__content{grid-column-start:1;grid-column-end:3;align-self:baseline;font-size:.9em}.yla-dialog__overlay .yla-dialog>.yla-dialog__body>p.label{padding:8px 0;align-self:stretch;font-size:.9em}.yla-dialog__overlay .yla-dialog>.yla-dialog__body>input,.yla-dialog__overlay .yla-dialog>.yla-dialog__body>textarea{color:#232323;padding:8px;border:1px solid #E5E5E5;font-size:.9em;min-height:37px}.yla-dialog__overlay .yla-dialog>.yla-dialog__body>.suggestion{position:absolute;display:block;padding:8px;background-color:#EEE;border:1px solid #E5E5E5}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer{padding:16px;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;-webkit-box-pack:end;justify-content:flex-end;border-top:1px solid #E5E5E5}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer>a{text-transform:uppercase;padding:0 8px;font-size:.9em;font-weight:600;border-bottom:1px dashed transparent}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer>a:hover{color:#F44336}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer>a:focus{outline:none;color:#F44336;border-bottom:1px dashed #F44336}.yla-dialog__overlay .yla-dialog>.yla-dialog__footer>i{width:19px;line-height:19px;text-align:center}

View file

@ -72,12 +72,9 @@
<a class="url" title="View original" :href="book.url" target="_blank">
{{getHostname(book.url)}}
</a>
<a title="Edit bookmark">
<a title="Edit bookmark" @click="showDialogEdit(idx)">
<i class="fas fa-pencil-alt"></i>
</a>
<a title="Edit tags">
<i class="fas fa-tags"></i>
</a>
<a title="Delete bookmark" @click="showDialogDelete([idx])">
<i class="fas fa-trash-alt"></i>
</a>
@ -178,6 +175,63 @@
}
});
},
showDialogEdit(idx) {
var book = JSON.parse(JSON.stringify(this.bookmarks[idx])),
strTags = book.tags.map(tag => tag.name).join(' ');
this.showDialog({
title: 'Edit Bookmark',
content: 'Edit the bookmark\'s data',
showLabel: true,
fields: [{
name: 'tags',
label: 'Tags',
value: strTags,
}, {
name: 'title',
label: 'Title',
value: book.title,
}, {
name: 'excerpt',
label: 'Excerpt',
type: 'area',
value: book.excerpt,
}],
mainText: 'OK',
secondText: 'Cancel',
mainClick: (data) => {
// Validate input
if (data.title.trim() === '') return;
// Prepare tags
var tags = data.tags
.toLowerCase()
.split(/\s+/g).map(tag => {
return {
name: tag
};
});
// Set new data
book.title = data.title.trim();
book.excerpt = data.excerpt.trim();
book.tags = tags;
// Send data
this.dialog.loading = true;
rest.put('/api/bookmarks', book)
.then((response) => {
this.dialog.loading = false;
this.dialog.visible = false;
this.bookmarks.splice(idx, 1, response.data);
})
.catch((error) => {
var errorMsg = (error.response ? error.response.data : error.message).trim();
this.showErrorDialog(errorMsg);
});
}
});
},
showDialogDelete(indices) {
// Check and prepare indices
if (!(indices instanceof Array)) return;

View file

@ -10,7 +10,7 @@ var YlaDialog = function () {
<slot>
<p class="yla-dialog__content">{{content}}</p>
<template v-for="(field,index) in formFields">
<p v-if="showLabel">{{field.label}} :</p>
<p v-if="showLabel" class="label">{{field.label}} :</p>
<textarea v-if="field.type === 'area'"
:style="{gridColumnEnd: showLabel ? null : 'span 2'}"
:placeholder="field.label"

View file

@ -52,12 +52,17 @@
grid-template-columns: max-content 1fr;
align-items: baseline;
grid-gap: 16px;
>.yla-dialog__content {
>p.yla-dialog__content {
grid-column-start: 1;
grid-column-end: 3;
align-self: baseline;
font-size: 0.9em;
}
>p.label {
padding: 8px 0;
align-self: stretch;
font-size: 0.9em;
}
>input,
>textarea {
color: @color;