Add method for renaming tag

This commit is contained in:
Radhi Fadlillah 2019-08-07 23:30:17 +07:00
parent 690bda85f7
commit ea73eaff64
6 changed files with 128 additions and 17 deletions

View file

@ -61,6 +61,9 @@ type DB interface {
// GetTags fetch list of tags and its frequency from database.
GetTags() ([]model.Tag, error)
// RenameTag change the name of a tag.
RenameTag(id int, newName string) error
// CreateNewID creates new id for specified table.
CreateNewID(table string) (int, error)
}

View file

@ -523,6 +523,12 @@ func (db *SQLiteDatabase) GetTags() ([]model.Tag, error) {
return tags, nil
}
// RenameTag change the name of a tag.
func (db *SQLiteDatabase) RenameTag(id int, newName string) error {
_, err := db.Exec(`UPDATE tag SET name = ? WHERE id = ?`, newName, id)
return err
}
// CreateNewID creates new ID for specified table
func (db *SQLiteDatabase) CreateNewID(table string) (int, error) {
var tableID int

View file

@ -61,7 +61,7 @@ var template = `
<p class="empty-message" v-if="!loading && listIsEmpty">No saved bookmarks yet :(</p>
<div class="loading-overlay" v-if="loading"><i class="fas fa-fw fa-spin fa-spinner"></i></div>
<custom-dialog id="dialog-tags" v-bind="dialogTags">
<a v-for="tag in tags" @click="filterTag(tag.name)">
<a v-for="(tag, idx) in tags" @click="tagClicked(idx, tag)">
{{tag.name}}<span>{{tag.nBookmarks}}</span>
</a>
</custom-dialog>
@ -95,11 +95,24 @@ export default {
dialogTags: {
visible: false,
editMode: false,
title: 'Existing Tags',
mainText: 'OK',
secondText: 'Rename Tags',
mainClick: () => {
this.dialogTags.visible = false;
if (this.dialogTags.editMode) {
this.dialogTags.editMode = false;
} else {
this.dialogTags.visible = false;
}
},
secondClick: () => {
this.dialogTags.editMode = true;
},
escPressed: () => {
this.dialogTags.visible = false;
this.dialogTags.editMode = false;
}
},
}
},
@ -108,6 +121,19 @@ export default {
return this.bookmarks.length <= 0;
}
},
watch: {
"dialogTags.editMode"(editMode) {
if (editMode) {
this.dialogTags.title = "Rename Tags";
this.dialogTags.mainText = "Cancel";
this.dialogTags.secondText = "";
} else {
this.dialogTags.title = "Existing Tags";
this.dialogTags.mainText = "OK";
this.dialogTags.secondText = "Rename Tags";
}
}
},
methods: {
reloadData() {
if (this.loading) return;
@ -223,15 +249,6 @@ export default {
this.$refs.bookmarksGrid.scrollTop = 0;
this.loadData();
},
filterTag(tagName) {
var rxSpace = /\s+/g,
newTag = rxSpace.test(tagName) ? `"#${tagName}"` : `#${tagName}`;
if (!this.search.includes(newTag)) {
this.search += ` ${newTag}`;
this.loadData();
}
},
toggleEditMode() {
this.selection = [];
this.editMode = !this.editMode;
@ -244,6 +261,23 @@ export default {
isSelected(bookId) {
return this.selection.findIndex(el => el.id === bookId) > -1;
},
tagClicked(idx, tag) {
if (!this.dialogTags.editMode) {
this.filterTag(tag.name);
} else {
this.dialogTags.visible = false;
this.showDialogRenameTag(idx, tag);
}
},
filterTag(tagName) {
var rxSpace = /\s+/g,
newTag = rxSpace.test(tagName) ? `"#${tagName}"` : `#${tagName}`;
if (!this.search.includes(newTag)) {
this.search += ` ${newTag}`;
this.loadData();
}
},
showDialogAdd() {
this.showDialog({
title: "New Bookmark",
@ -631,7 +665,55 @@ export default {
},
showDialogTags() {
this.dialogTags.visible = true;
}
},
showDialogRenameTag(idx, tag) {
this.showDialog({
title: "Rename Tag",
content: `Change the name for tag "#${tag.name}"`,
fields: [{
name: "newName",
label: "New tag name",
value: tag.name,
}],
mainText: "OK",
secondText: "Cancel",
secondClick: () => {
this.dialog.visible = false;
this.dialogTags.visible = true;
},
mainClick: (data) => {
// Send data
tag.name = data.newName;
this.dialog.loading = true;
fetch("/api/tag", {
method: "PUT",
body: JSON.stringify(tag),
headers: {
"Content-Type": "application/json",
},
})
.then(response => {
if (!response.ok) throw response;
return response.json();
})
.then(() => {
this.dialog.loading = false;
this.dialog.visible = false;
this.dialogTags.visible = true;
this.tags.splice(idx, 1, tag);
})
.catch(err => {
this.dialog.loading = false;
this.dialogTags.visible = false;
this.dialogTags.editMode = false;
err.text().then(msg => {
this.showErrorDialog(msg, err.status);
})
});
},
});
},
},
mounted() {
// Prepare history state watcher
@ -658,8 +740,9 @@ export default {
initialPage = url.hash.replace(/^([^?]+).*$/, "$1");
if (initialPage === "home") {
var search = url.hash.replace(/^.*(\?|&)search=([^?&]*).*$/, "$2"),
page = url.hash.replace(/^.*(\?|&)page=(\d+).*$/, "$2");
var urlHash = url.hash.replace(initialPage, ""),
search = urlHash.replace(/^.*(\?|&)search=([^?&]*).*$/, "$2"),
page = urlHash.replace(/^.*(\?|&)page=(\d+).*$/, "$2");
this.search = decodeURIComponent(search) || "";
this.page = parseInt(page) || 1;

File diff suppressed because one or more lines are too long

View file

@ -194,6 +194,24 @@ func (h *handler) apiGetTags(w http.ResponseWriter, r *http.Request, ps httprout
checkError(err)
}
// apiRenameTag is handler for PUT /api/tag
func (h *handler) apiRenameTag(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// Make sure session still valid
err := h.validateSession(r)
checkError(err)
// Decode request
tag := model.Tag{}
err = json.NewDecoder(r.Body).Decode(&tag)
checkError(err)
// Update name
err = h.DB.RenameTag(tag.ID, tag.Name)
checkError(err)
fmt.Fprint(w, 1)
}
// apiInsertBookmark is handler for POST /api/bookmark
func (h *handler) apiInsertBookmark(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
// Make sure session still valid

View file

@ -48,6 +48,7 @@ func ServeApp(DB database.DB, dataDir string, port int) error {
router.POST("/api/logout", hdl.apiLogout)
router.GET("/api/bookmarks", hdl.apiGetBookmarks)
router.GET("/api/tags", hdl.apiGetTags)
router.PUT("/api/tag", hdl.apiRenameTag)
router.POST("/api/bookmarks", hdl.apiInsertBookmark)
router.DELETE("/api/bookmarks", hdl.apiDeleteBookmark)
router.PUT("/api/bookmarks", hdl.apiUpdateBookmark)