diff --git a/cmd/serve/web-handler-api.go b/cmd/serve/web-handler-api.go index 02d5e1ec..54a75c36 100644 --- a/cmd/serve/web-handler-api.go +++ b/cmd/serve/web-handler-api.go @@ -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) diff --git a/database/sqlite.go b/database/sqlite.go index 833a7afa..73a77416 100644 --- a/database/sqlite.go +++ b/database/sqlite.go @@ -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 { diff --git a/view/css/yla-dialog.css b/view/css/yla-dialog.css index 198bf497..189b8f59 100644 --- a/view/css/yla-dialog.css +++ b/view/css/yla-dialog.css @@ -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} \ No newline at end of file +.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} \ No newline at end of file diff --git a/view/index.html b/view/index.html index a1c5165d..005ee600 100644 --- a/view/index.html +++ b/view/index.html @@ -72,12 +72,9 @@ {{getHostname(book.url)}} - + - - - @@ -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; diff --git a/view/js/component/yla-dialog.js b/view/js/component/yla-dialog.js index 78dbdb12..836eda6b 100644 --- a/view/js/component/yla-dialog.js +++ b/view/js/component/yla-dialog.js @@ -10,7 +10,7 @@ var YlaDialog = function () {

{{content}}