Implement button for update cache

This commit is contained in:
Radhi Fadlillah 2018-05-19 16:11:18 +07:00
parent fec02adfc2
commit 961d61d4a9
6 changed files with 149 additions and 27 deletions

File diff suppressed because one or more lines are too long

View file

@ -45,6 +45,7 @@ func NewServeCmd(db dt.Database, dataDir string) *cobra.Command {
router.GET("/api/bookmarks", hdl.apiGetBookmarks)
router.GET("/api/tags", hdl.apiGetTags)
router.POST("/api/bookmarks", hdl.apiInsertBookmark)
router.PUT("/api/cache", hdl.apiUpdateCache)
router.PUT("/api/bookmarks", hdl.apiUpdateBookmark)
router.DELETE("/api/bookmarks", hdl.apiDeleteBookmark)

View file

@ -9,6 +9,7 @@ import (
"os"
fp "path/filepath"
"strings"
"sync"
"time"
"github.com/RadhiFadlillah/shiori/model"
@ -188,7 +189,7 @@ func (h *webHandler) apiUpdateBookmark(w http.ResponseWriter, r *http.Request, p
checkError(err)
// Validate input
if request.Title != "" {
if request.Title == "" {
panic(fmt.Errorf("Title must not empty"))
}
@ -232,6 +233,84 @@ func (h *webHandler) apiUpdateBookmark(w http.ResponseWriter, r *http.Request, p
checkError(err)
}
// apiUpdateCache is handler for PUT /api/cache
func (h *webHandler) apiUpdateCache(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// Check token
err := h.checkAPIToken(r)
checkError(err)
// Decode request
indices := []string{}
err = json.NewDecoder(r.Body).Decode(&indices)
checkError(err)
// Prepare wait group
wg := sync.WaitGroup{}
// Fetch bookmarks from database
books, err := h.db.GetBookmarks(false, indices...)
checkError(err)
// Download new cache data
for i, book := range books {
wg.Add(1)
go func(pos int, book model.Bookmark) {
defer wg.Done()
// Parse URL
parsedURL, err := nurl.ParseRequestURI(book.URL)
if err != nil || parsedURL.Host == "" {
return
}
// Fetch data from internet
article, err := readability.Parse(parsedURL, 10*time.Second)
if err != nil {
return
}
book.Excerpt = article.Meta.Excerpt
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
// Make sure title is not empty
if article.Meta.Title != "" {
book.Title = article.Meta.Title
}
// Check if book has content
if book.Content != "" {
book.HasContent = true
}
// Update bookmark image in local disk
imgPath := fp.Join(h.dataDir, "thumb", fmt.Sprintf("%d", book.ID))
err = downloadFile(article.Meta.Image, imgPath, 20*time.Second)
if err == nil {
book.ImageURL = fmt.Sprintf("/thumb/%d", book.ID)
}
books[pos] = book
}(i, book)
}
// Wait until all finished
wg.Wait()
// Update database
res, err := h.db.UpdateBookmarks(books...)
checkError(err)
// Return new saved result
err = json.NewEncoder(w).Encode(&res)
checkError(err)
}
func downloadFile(url, dstPath string, timeout time.Duration) error {
// Fetch data from URL
client := &http.Client{Timeout: timeout}

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>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}
.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;resize:vertical}.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

@ -63,7 +63,7 @@
</div>
<div id="grid">
<div class="bookmark" v-for="(book, idx) in bookmarks">
<a class="bookmark-content" :href="book.hasContent ? '/bookmark/'+book.id : null" target="_blank">
<a class="bookmark-content" :href="book.hasContent ? '/bookmark/'+book.id : null" :title="book.hasContent ? 'View cache' : null" target="_blank">
<img v-if="book.imageURL !== ''" :src="book.imageURL">
<p class="title">{{book.title}}</p>
<p class="excerpt" v-if="book.imageURL === ''">{{book.excerpt}}</p>
@ -78,7 +78,7 @@
<a title="Delete bookmark" @click="showDialogDelete([idx])">
<i class="fas fa-trash-alt"></i>
</a>
<a title="Update cache">
<a title="Update cache" @click="showDialogUpdateCache([idx])">
<i class="fas fa-cloud-download-alt"></i>
</a>
</div>
@ -184,10 +184,6 @@
content: 'Edit the bookmark\'s data',
showLabel: true,
fields: [{
name: 'tags',
label: 'Tags',
value: strTags,
}, {
name: 'title',
label: 'Title',
value: book.title,
@ -196,6 +192,10 @@
label: 'Excerpt',
type: 'area',
value: book.excerpt,
}, {
name: 'tags',
label: 'Tags',
value: strTags,
}],
mainText: 'OK',
secondText: 'Cancel',
@ -243,7 +243,6 @@
content = "Delete the selected bookmarks ? This action is irreversible.";
if (indices.length === 1) {
var book = this.bookmarks[indices[0]];
title = "Delete Bookmark";
content = "Are you sure ? This action is irreversible.";
}
@ -279,6 +278,48 @@
}
});
},
showDialogUpdateCache(indices) {
// Check and prepare indices
if (!(indices instanceof Array)) return;
if (indices.length === 0) return;
indices.sort();
// Get list of bookmark ID
var listID = [];
for (var i = 0; i < indices.length; i++) {
listID.push('' + this.bookmarks[indices[i]].id);
}
// Show dialog
this.showDialog({
title: 'Update Cache',
content: 'Update cache for selected bookmarks ? This action is irreversible.',
mainText: 'Yes',
secondText: 'No',
mainClick: () => {
this.dialog.loading = true;
rest.put('/api/cache/', listID)
.then((response) => {
this.dialog.loading = false;
this.dialog.visible = false;
response.data.forEach(book => {
for (var i = 0; i < indices.length; i++) {
var idx = indices[i];
if (book.id === this.bookmarks[idx].id) {
this.bookmarks.splice(idx, 1, book);
break;
}
}
});
})
.catch((error) => {
var errorMsg = (error.response ? error.response.data : error.message).trim();
this.showErrorDialog(errorMsg);
});
}
});
},
getHostname(url) {
parser = document.createElement('a');
parser.href = url;

View file

@ -70,6 +70,7 @@
border: 1px solid @border;
font-size: 0.9em;
min-height: 37px;
resize: vertical;
}
>.suggestion {
position: absolute;