mirror of
https://github.com/go-shiori/shiori.git
synced 2025-01-31 04:09:12 +08:00
Implement button for update cache
This commit is contained in:
parent
fec02adfc2
commit
961d61d4a9
6 changed files with 149 additions and 27 deletions
File diff suppressed because one or more lines are too long
|
@ -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)
|
||||
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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}
|
|
@ -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;
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
border: 1px solid @border;
|
||||
font-size: 0.9em;
|
||||
min-height: 37px;
|
||||
resize: vertical;
|
||||
}
|
||||
>.suggestion {
|
||||
position: absolute;
|
||||
|
|
Loading…
Reference in a new issue