mirror of
https://github.com/go-shiori/shiori.git
synced 2024-11-15 05:34:45 +08:00
Continue updating UI
This commit is contained in:
parent
0ffd6b3231
commit
9048fe4bdc
11 changed files with 225 additions and 116 deletions
File diff suppressed because one or more lines are too long
|
@ -100,28 +100,48 @@ func (h *webHandler) apiInsertBookmark(w http.ResponseWriter, r *http.Request, p
|
|||
err = json.NewDecoder(r.Body).Decode(&book)
|
||||
checkError(err)
|
||||
|
||||
// Make sure URL valid
|
||||
parsedURL, err := nurl.ParseRequestURI(book.URL)
|
||||
if err != nil || parsedURL.Host == "" {
|
||||
panic(fmt.Errorf("URL is not valid"))
|
||||
}
|
||||
|
||||
// Clear UTM parameter from URL
|
||||
clearUTMParams(parsedURL)
|
||||
book.URL = parsedURL.String()
|
||||
|
||||
// Get new bookmark id
|
||||
book.ID, err = h.db.GetNewID("bookmark")
|
||||
checkError(err)
|
||||
|
||||
// Fetch data from internet
|
||||
article, err := readability.Parse(book.URL, 20*time.Second)
|
||||
checkError(err)
|
||||
article, _ := readability.Parse(parsedURL, 20*time.Second)
|
||||
|
||||
book.URL = article.URL
|
||||
book.Title = article.Meta.Title
|
||||
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
|
||||
|
||||
// If title and excerpt doesnt have submitted value, use from article
|
||||
if book.Title == "" {
|
||||
book.Title = article.Meta.Title
|
||||
}
|
||||
|
||||
if book.Excerpt == "" {
|
||||
book.Excerpt = article.Meta.Excerpt
|
||||
}
|
||||
|
||||
// Make sure title is not empty
|
||||
if book.Title == "" {
|
||||
book.Title = book.URL
|
||||
}
|
||||
|
||||
// Check if book has content
|
||||
if book.Content != "" {
|
||||
book.HasContent = true
|
||||
}
|
||||
|
||||
// Save bookmark image to local disk
|
||||
imgPath := fp.Join(h.dataDir, "thumb", fmt.Sprintf("%d", book.ID))
|
||||
err = downloadFile(article.Meta.Image, imgPath, 20*time.Second)
|
||||
|
@ -138,6 +158,24 @@ func (h *webHandler) apiInsertBookmark(w http.ResponseWriter, r *http.Request, p
|
|||
checkError(err)
|
||||
}
|
||||
|
||||
// apiDeleteBookmarks is handler for DELETE /api/bookmark
|
||||
func (h *webHandler) apiDeleteBookmark(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)
|
||||
|
||||
// Delete bookmarks
|
||||
err = h.db.DeleteBookmarks(indices...)
|
||||
checkError(err)
|
||||
|
||||
fmt.Fprint(w, 1)
|
||||
}
|
||||
|
||||
// apiUpdateBookmark is handler for PUT /api/bookmark
|
||||
func (h *webHandler) apiUpdateBookmark(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
// Check token
|
||||
|
@ -157,6 +195,7 @@ func (h *webHandler) apiUpdateBookmark(w http.ResponseWriter, r *http.Request, p
|
|||
if err != nil || parsedURL.Host == "" {
|
||||
panic(fmt.Errorf("URL is not valid"))
|
||||
}
|
||||
clearUTMParams(parsedURL)
|
||||
|
||||
// Get existing bookmark from database
|
||||
bookmarks, err := h.db.GetBookmarks(true, fmt.Sprintf("%d", request.ID))
|
||||
|
@ -167,10 +206,10 @@ func (h *webHandler) apiUpdateBookmark(w http.ResponseWriter, r *http.Request, p
|
|||
}
|
||||
|
||||
book := bookmarks[0]
|
||||
book.URL = request.URL
|
||||
book.URL = parsedURL.String()
|
||||
|
||||
// Fetch data from internet
|
||||
article, err := readability.Parse(book.URL, 10*time.Second)
|
||||
article, err := readability.Parse(parsedURL, 10*time.Second)
|
||||
checkError(err)
|
||||
|
||||
book.ImageURL = article.Meta.Image
|
||||
|
@ -243,24 +282,6 @@ func (h *webHandler) apiUpdateBookmark(w http.ResponseWriter, r *http.Request, p
|
|||
checkError(err)
|
||||
}
|
||||
|
||||
// apiDeleteBookmarks is handler for DELETE /api/bookmark
|
||||
func (h *webHandler) apiDeleteBookmark(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)
|
||||
|
||||
// Delete bookmarks
|
||||
err = h.db.DeleteBookmarks(indices...)
|
||||
checkError(err)
|
||||
|
||||
fmt.Fprint(w, 1)
|
||||
}
|
||||
|
||||
func downloadFile(url, dstPath string, timeout time.Duration) error {
|
||||
// Fetch data from URL
|
||||
client := &http.Client{Timeout: timeout}
|
||||
|
@ -291,3 +312,14 @@ func downloadFile(url, dstPath string, timeout time.Duration) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func clearUTMParams(url *nurl.URL) {
|
||||
newQuery := nurl.Values{}
|
||||
for key, value := range url.Query() {
|
||||
if !strings.HasPrefix(key, "utm_") {
|
||||
newQuery[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
url.RawQuery = newQuery.Encode()
|
||||
}
|
||||
|
|
|
@ -181,12 +181,27 @@ func (db *SQLiteDatabase) GetBookmarks(withContent bool, indices ...string) ([]m
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Create query
|
||||
query := `SELECT
|
||||
b.id, b.url, b.title, b.image_url, b.excerpt, b.author,
|
||||
b.min_read_time, b.max_read_time, b.modified, bc.content <> "" has_content
|
||||
FROM bookmark b
|
||||
LEFT JOIN bookmark_content bc ON bc.docid = b.id`
|
||||
|
||||
if withContent {
|
||||
query = `SELECT
|
||||
b.id, b.url, b.title, b.image_url, b.excerpt, b.author,
|
||||
b.min_read_time, b.max_read_time, b.modified, bc.content, bc.html
|
||||
FROM bookmark b
|
||||
LEFT JOIN bookmark_content bc ON bc.docid = b.id`
|
||||
}
|
||||
|
||||
// Prepare where clause
|
||||
args := []interface{}{}
|
||||
whereClause := " WHERE 1"
|
||||
|
||||
if len(listIndex) > 0 {
|
||||
whereClause = " WHERE id IN ("
|
||||
whereClause = " WHERE b.id IN ("
|
||||
for _, idx := range listIndex {
|
||||
args = append(args, idx)
|
||||
whereClause += "?,"
|
||||
|
@ -197,32 +212,21 @@ func (db *SQLiteDatabase) GetBookmarks(withContent bool, indices ...string) ([]m
|
|||
}
|
||||
|
||||
// Fetch bookmarks
|
||||
query := `SELECT id,
|
||||
url, title, image_url, excerpt, author,
|
||||
min_read_time, max_read_time, modified
|
||||
FROM bookmark` + whereClause
|
||||
|
||||
query += whereClause
|
||||
bookmarks := []model.Bookmark{}
|
||||
err = db.Select(&bookmarks, query, args...)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Fetch tags and contents for each bookmarks
|
||||
// Fetch tags for each bookmarks
|
||||
stmtGetTags, err := db.Preparex(`SELECT t.id, t.name
|
||||
FROM bookmark_tag bt LEFT JOIN tag t ON bt.tag_id = t.id
|
||||
WHERE bt.bookmark_id = ? ORDER BY t.name`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stmtGetContent, err := db.Preparex(`SELECT title, content, html FROM bookmark_content WHERE docid = ?`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer stmtGetTags.Close()
|
||||
defer stmtGetContent.Close()
|
||||
|
||||
for i, book := range bookmarks {
|
||||
book.Tags = []model.Tag{}
|
||||
|
@ -231,13 +235,6 @@ func (db *SQLiteDatabase) GetBookmarks(withContent bool, indices ...string) ([]m
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if withContent {
|
||||
err = stmtGetContent.Get(&book, book.ID)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
bookmarks[i] = book
|
||||
}
|
||||
|
||||
|
@ -300,22 +297,25 @@ func (db *SQLiteDatabase) DeleteBookmarks(indices ...string) (err error) {
|
|||
|
||||
// SearchBookmarks search bookmarks by the keyword or tags.
|
||||
func (db *SQLiteDatabase) SearchBookmarks(orderLatest bool, keyword string, tags ...string) ([]model.Bookmark, error) {
|
||||
// Create initial variable
|
||||
keyword = strings.TrimSpace(keyword)
|
||||
whereClause := "WHERE 1"
|
||||
// Prepare query
|
||||
args := []interface{}{}
|
||||
query := `SELECT
|
||||
b.id, b.url, b.title, b.image_url, b.excerpt, b.author,
|
||||
b.min_read_time, b.max_read_time, b.modified, bc.content <> "" has_content
|
||||
FROM bookmark b
|
||||
LEFT JOIN bookmark_content bc ON bc.docid = b.id
|
||||
WHERE 1`
|
||||
|
||||
// Create where clause for keyword
|
||||
keyword = strings.TrimSpace(keyword)
|
||||
if keyword != "" {
|
||||
whereClause += ` AND (url LIKE ? OR id IN (
|
||||
SELECT docid id FROM bookmark_content
|
||||
WHERE title MATCH ? OR content MATCH ?))`
|
||||
query += ` AND (b.url LIKE ? OR bc.title MATCH ? OR bc.content MATCH ?)`
|
||||
args = append(args, "%"+keyword+"%", keyword, keyword)
|
||||
}
|
||||
|
||||
// Create where clause for tags
|
||||
if len(tags) > 0 {
|
||||
whereTagClause := ` AND id IN (
|
||||
whereTagClause := ` AND b.id IN (
|
||||
SELECT bookmark_id FROM bookmark_tag
|
||||
WHERE tag_id IN (SELECT id FROM tag WHERE name IN (`
|
||||
|
||||
|
@ -328,19 +328,15 @@ func (db *SQLiteDatabase) SearchBookmarks(orderLatest bool, keyword string, tags
|
|||
whereTagClause += `)) GROUP BY bookmark_id HAVING COUNT(bookmark_id) >= ?)`
|
||||
args = append(args, len(tags))
|
||||
|
||||
whereClause += whereTagClause
|
||||
query += whereTagClause
|
||||
}
|
||||
|
||||
// Search bookmarks
|
||||
query := `SELECT id,
|
||||
url, title, image_url, excerpt, author,
|
||||
min_read_time, max_read_time, modified
|
||||
FROM bookmark ` + whereClause
|
||||
|
||||
// Set order clause
|
||||
if orderLatest {
|
||||
query += ` ORDER BY id DESC`
|
||||
}
|
||||
|
||||
// Fetch bookmarks
|
||||
bookmarks := []model.Bookmark{}
|
||||
err := db.Select(&bookmarks, query, args...)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
|
|
|
@ -21,6 +21,7 @@ type Bookmark struct {
|
|||
Modified string `db:"modified" json:"modified"`
|
||||
Content string `db:"content" json:"-"`
|
||||
HTML string `db:"html" json:"-"`
|
||||
HasContent bool `db:"has_content" json:"hasContent"`
|
||||
Tags []Tag `json:"tags"`
|
||||
}
|
||||
|
||||
|
|
|
@ -1131,24 +1131,9 @@ func estimateReadTime(articleContent *goquery.Selection) (int, int) {
|
|||
}
|
||||
|
||||
// Parse an URL to readability format
|
||||
func Parse(url string, timeout time.Duration) (Article, error) {
|
||||
// Make sure url is valid
|
||||
parsedURL, err := nurl.ParseRequestURI(url)
|
||||
if err != nil {
|
||||
return Article{}, err
|
||||
}
|
||||
|
||||
// Clear UTM parameters from URL
|
||||
newQuery := nurl.Values{}
|
||||
for key, value := range parsedURL.Query() {
|
||||
if !strings.HasPrefix(key, "utm_") {
|
||||
newQuery[key] = value
|
||||
}
|
||||
}
|
||||
parsedURL.RawQuery = newQuery.Encode()
|
||||
|
||||
func Parse(url *nurl.URL, timeout time.Duration) (Article, error) {
|
||||
// Fetch page
|
||||
doc, err := fetchURL(parsedURL, timeout)
|
||||
doc, err := fetchURL(url, timeout)
|
||||
if err != nil {
|
||||
return Article{}, err
|
||||
}
|
||||
|
@ -1165,7 +1150,7 @@ func Parse(url string, timeout time.Duration) (Article, error) {
|
|||
}
|
||||
|
||||
// Post process content
|
||||
postProcessContent(articleContent, parsedURL)
|
||||
postProcessContent(articleContent, url)
|
||||
|
||||
// Estimate read time
|
||||
minTime, maxTime := estimateReadTime(articleContent)
|
||||
|
@ -1188,7 +1173,7 @@ func Parse(url string, timeout time.Duration) (Article, error) {
|
|||
htmlContent := getHTMLContent(articleContent)
|
||||
|
||||
article := Article{
|
||||
URL: parsedURL.String(),
|
||||
URL: url.String(),
|
||||
Meta: metadata,
|
||||
Content: textContent,
|
||||
RawContent: htmlContent,
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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;font-size:.9em;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}.yla-dialog__overlay .yla-dialog>.yla-dialog__body>input{color:#232323;padding:8px;border:1px solid #E5E5E5}.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}.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>.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}
|
|
@ -62,8 +62,8 @@
|
|||
</a>
|
||||
</div>
|
||||
<div id="grid">
|
||||
<div class="bookmark" v-for="book in bookmarks">
|
||||
<a class="bookmark-content" :href="'/bookmark/'+book.id" target="_blank">
|
||||
<div class="bookmark" v-for="(book, idx) in bookmarks">
|
||||
<a class="bookmark-content" :href="book.hasContent ? '/bookmark/'+book.id : 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="Edit tags">
|
||||
<i class="fas fa-tags"></i>
|
||||
</a>
|
||||
<a title="Delete bookmark">
|
||||
<a title="Delete bookmark" @click="showDialogDelete([idx])">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</a>
|
||||
<a title="Update cache">
|
||||
|
@ -86,6 +86,7 @@
|
|||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<yla-dialog v-bind="dialog"></yla-dialog>
|
||||
|
@ -119,7 +120,7 @@
|
|||
.then((response) => {
|
||||
this.loading = false;
|
||||
this.bookmarks = response.data;
|
||||
console.log(JSON.stringify(response.data));
|
||||
console.log(JSON.stringify(response.data, '', ' '));
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = (error.response ? error.response.data : error.message).trim();
|
||||
|
@ -130,22 +131,92 @@
|
|||
showDialogAdd() {
|
||||
this.showDialog({
|
||||
title: 'New Bookmark',
|
||||
content: 'Save an URL to bookmark',
|
||||
content: 'Create a new bookmark',
|
||||
fields: [{
|
||||
name: 'url',
|
||||
label: 'http://...',
|
||||
label: 'Url, start with http://...',
|
||||
}, {
|
||||
name: 'tags',
|
||||
label: 'Space separated tags (optional)'
|
||||
}, {
|
||||
name: 'title',
|
||||
label: 'Custom title (optional)'
|
||||
}, {
|
||||
name: 'excerpt',
|
||||
label: 'Custom excerpt (optional)',
|
||||
type: 'area'
|
||||
}],
|
||||
mainText: 'OK',
|
||||
secondText: 'Cancel',
|
||||
mainClick: (data) => {
|
||||
// Prepare tags
|
||||
var tags = data.tags
|
||||
.toLowerCase()
|
||||
.split(/\s+/g).map(tag => {
|
||||
return {
|
||||
name: tag
|
||||
};
|
||||
});
|
||||
|
||||
// Send data
|
||||
this.dialog.loading = true;
|
||||
rest.post('/api/bookmarks', {
|
||||
url: data.url,
|
||||
url: data.url.trim(),
|
||||
title: data.title.trim(),
|
||||
excerpt: data.excerpt.trim(),
|
||||
tags: tags
|
||||
})
|
||||
.then((response) => {
|
||||
this.dialog.loading = false;
|
||||
this.dialog.visible = false;
|
||||
this.bookmarks.unshift(response.data);
|
||||
this.bookmarks.splice(0, 0, 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;
|
||||
if (indices.length === 0) return;
|
||||
indices.sort();
|
||||
|
||||
// Create title andd content
|
||||
var title = "Delete Bookmarks",
|
||||
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.";
|
||||
}
|
||||
|
||||
// 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: title,
|
||||
content: content,
|
||||
mainText: 'Yes',
|
||||
secondText: 'No',
|
||||
mainClick: () => {
|
||||
this.dialog.loading = true;
|
||||
rest.delete('/api/bookmarks/', {
|
||||
data: listID
|
||||
})
|
||||
.then((response) => {
|
||||
this.dialog.loading = false;
|
||||
this.dialog.visible = false;
|
||||
for (var i = indices.length - 1; i >= 0; i--) {
|
||||
this.bookmarks.splice(indices[i], 1);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
var errorMsg = (error.response ? error.response.data : error.message).trim();
|
||||
|
@ -157,7 +228,7 @@
|
|||
getHostname(url) {
|
||||
parser = document.createElement('a');
|
||||
parser.href = url;
|
||||
return parser.hostname;
|
||||
return parser.hostname.replace(/^www\./g, '');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
|
|
@ -11,7 +11,17 @@ var YlaDialog = function () {
|
|||
<p class="yla-dialog__content">{{content}}</p>
|
||||
<template v-for="(field,index) in formFields">
|
||||
<p v-if="showLabel">{{field.label}} :</p>
|
||||
<input :style="{gridColumnEnd: showLabel ? null : 'span 2'}"
|
||||
<textarea v-if="field.type === 'area'"
|
||||
:style="{gridColumnEnd: showLabel ? null : 'span 2'}"
|
||||
:placeholder="field.label"
|
||||
:tabindex="index+1"
|
||||
ref="input"
|
||||
v-model="field.value"
|
||||
@focus="$event.target.select()"
|
||||
@keyup="handleInput(index)">
|
||||
</textarea>
|
||||
<input v-else
|
||||
:style="{gridColumnEnd: showLabel ? null : 'span 2'}"
|
||||
:type="fieldType(field)"
|
||||
:placeholder="field.label"
|
||||
:tabindex="index+1"
|
||||
|
|
|
@ -204,7 +204,12 @@ a {
|
|||
grid-template-rows: auto;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
grid-gap: 16px;
|
||||
padding: 16px;
|
||||
padding: 16px 16px 0;
|
||||
>div:last-child {
|
||||
grid-column-start: 1;
|
||||
grid-column-end: -1;
|
||||
height: 1px;
|
||||
}
|
||||
.bookmark {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
@ -212,10 +217,8 @@ a {
|
|||
border: 1px solid @border;
|
||||
background-color: @contentBg;
|
||||
height: 100%;
|
||||
&:last-child {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:focus {
|
||||
.bookmark-menu>a {
|
||||
display: block;
|
||||
}
|
||||
|
@ -224,30 +227,37 @@ a {
|
|||
display: block;
|
||||
position: relative;
|
||||
flex: 1;
|
||||
&:hover,
|
||||
&:focus {
|
||||
.title {
|
||||
color: @main;
|
||||
cursor: default;
|
||||
&[href] {
|
||||
cursor: pointer;
|
||||
&:hover,
|
||||
&:focus {
|
||||
.title {
|
||||
color: @main;
|
||||
}
|
||||
}
|
||||
}
|
||||
>*:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.title {
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
font-size: 1.2em;
|
||||
line-height: 1.3em;
|
||||
max-height: 5.2em;
|
||||
font-weight: 600;
|
||||
padding: 0 16px;
|
||||
color: @color;
|
||||
&:first-child {
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
.excerpt {
|
||||
color: @color;
|
||||
padding: 0 16px;
|
||||
padding: 8px 16px 0;
|
||||
text-overflow: ellipsis;
|
||||
word-wrap: break-word;
|
||||
overflow: hidden;
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
max-height: 100%;
|
||||
min-height: 80px;
|
||||
min-width: 0;
|
||||
font-size: 0.9em;
|
||||
overflow: auto;
|
||||
grid-template-columns: max-content 1fr;
|
||||
align-items: baseline;
|
||||
|
@ -57,11 +56,15 @@
|
|||
grid-column-start: 1;
|
||||
grid-column-end: 3;
|
||||
align-self: baseline;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
>input {
|
||||
>input,
|
||||
>textarea {
|
||||
color: @color;
|
||||
padding: 8px;
|
||||
border: 1px solid @border;
|
||||
font-size: 0.9em;
|
||||
min-height: 37px;
|
||||
}
|
||||
>.suggestion {
|
||||
position: absolute;
|
||||
|
@ -82,6 +85,7 @@
|
|||
padding: 0 8px;
|
||||
font-size: 0.9em;
|
||||
font-weight: 600;
|
||||
border-bottom: 1px dashed transparent;
|
||||
&:hover {
|
||||
color: @main;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue