2018-02-11 22:00:56 +08:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< meta http-equiv = "X-UA-Compatible" content = "ie=edge" >
< link rel = "stylesheet" href = "/css/fontawesome.css" >
< link rel = "stylesheet" href = "/css/source-sans-pro.css" >
2018-05-18 15:07:15 +08:00
< link rel = "stylesheet" href = "/css/yla-dialog.css" >
< link rel = "stylesheet" href = "/css/yla-tooltip.css" >
< link rel = "stylesheet" href = "/css/stylesheet.css" >
2018-02-11 22:00:56 +08:00
< script src = "/js/vue.js" > < / script >
< script src = "/js/axios.js" > < / script >
2018-02-22 17:48:36 +08:00
< script src = "/js/js-cookie.js" > < / script >
2018-05-18 15:07:15 +08:00
< script src = "/js/component/yla-tooltip.js" > < / script >
< script src = "/js/component/yla-dialog.js" > < / script >
< script src = "/js/page/base.js" > < / script >
2018-02-26 14:43:17 +08:00
< link rel = "apple-touch-icon-precomposed" sizes = "144x144" href = "/res/apple-touch-icon-144x144.png" / >
< link rel = "apple-touch-icon-precomposed" sizes = "152x152" href = "/res/apple-touch-icon-152x152.png" / >
< link rel = "icon" type = "image/png" href = "/res/favicon-32x32.png" sizes = "32x32" / >
< link rel = "icon" type = "image/png" href = "/res/favicon-16x16.png" sizes = "16x16" / >
2018-02-11 22:00:56 +08:00
< title > Shiori - Bookmarks Manager< / title >
< / head >
< body >
2018-06-07 12:15:58 +08:00
< div id = "index-page" class = "page" :class = "{night: options.nightMode}" >
2018-05-18 15:07:15 +08:00
< div id = "sidebar" >
< p id = "logo" > 栞< / p >
2018-05-23 17:33:06 +08:00
< yla-tooltip placement = "right" content = "Reload data" >
< a @ click = "reloadData" v-show = "!editMode" >
< i class = "fas fa-sync-alt fa-fw" :class = "loading && 'fa-spin'" > < / i >
< / a >
< / yla-tooltip >
< yla-tooltip placement = "right" content = "Add new bookmark" >
2018-06-14 11:32:51 +08:00
< a @ click = "showDialogAdd" v-show = "!editMode && !loading" >
2018-06-06 22:48:46 +08:00
< span > +Shiori< / span >
2018-05-23 17:33:06 +08:00
< i class = "fas fa-plus fa-fw" > < / i >
< / a >
< / yla-tooltip >
< yla-tooltip placement = "right" :content = "editMode ? 'Cancel batch edit' : 'Batch edit'" >
2018-05-24 13:39:36 +08:00
< a @ click = "toggleEditMode" v-show = "!loading" >
2018-05-23 17:33:06 +08:00
< i class = "fas fa-fw" :class = "editMode ? 'fa-times' : 'fa-pencil-alt'" > < / i >
2018-05-18 15:07:15 +08:00
< / a >
< / yla-tooltip >
2018-05-24 13:39:36 +08:00
< yla-tooltip placement = "right" content = "Show tags" >
< a @ click = "showDialogTags" v-show = "!editMode && !loading" >
< i class = "fas fa-fw fa-tags" > < / i >
< / a >
< / yla-tooltip >
2018-05-18 15:07:15 +08:00
< div class = "spacer" > < / div >
2018-06-14 11:32:51 +08:00
< yla-tooltip placement = "right" content = "About" >
< a @ click = "showDialogAbout" >
< i class = "fas fa-fw fa-info-circle" > < / i >
< / a >
< / yla-tooltip >
2018-06-07 12:15:58 +08:00
< yla-tooltip placement = "right" content = "Options" >
< a @ click = "showDialogOptions" >
< i class = "fas fa-fw fa-cog" > < / i >
2018-05-18 15:07:15 +08:00
< / a >
< / yla-tooltip >
< yla-tooltip placement = "right" content = "Log out" >
2018-05-23 21:14:26 +08:00
< a @ click = "showDialogLogout" >
2018-05-18 15:07:15 +08:00
< i class = "fas fa-sign-out-alt fa-fw" > < / i >
< / a >
< / yla-tooltip >
2018-02-11 22:00:56 +08:00
< / div >
2018-05-18 15:07:15 +08:00
< div id = "body" >
2018-05-22 23:35:16 +08:00
< div id = "header" class = "header" v-if = "!editMode" >
2018-05-21 15:03:08 +08:00
< input type = "text" v-model . trim = "search" placeholder = "Search bookmarks by url, tags, title or content" @ focus = "$event.target.select()" @ keyup . enter = "loadData" >
< a title = "Search" @ click = "loadData" >
2018-05-18 15:07:15 +08:00
< i class = "fas fa-search fa-fw" > < / i >
< / a >
2018-02-14 12:41:43 +08:00
< / div >
2018-05-22 23:35:16 +08:00
< div id = "batch-edit" class = "header" v-if = "editMode" >
< p > {{selected.length}} Items selected< / p >
< a :class = "{disabled: selected.length === 0}" @ click = "showDialogDelete(selected)" >
< i class = "fas fa-fw fa-trash-alt" > < / i >
2018-05-24 00:12:16 +08:00
< span > Delete< / span >
2018-05-22 23:35:16 +08:00
< / a >
2018-05-23 17:33:06 +08:00
< a :class = "{disabled: selected.length === 0}" @ click = "showDialogAddTags(selected)" >
2018-05-22 23:35:16 +08:00
< i class = "fas fa-fw fa-tags" > < / i >
2018-05-24 00:12:16 +08:00
< span > Add tags< / span >
2018-05-22 23:35:16 +08:00
< / a >
< a :class = "{disabled: selected.length === 0}" @ click = "showDialogUpdateCache(selected)" >
< i class = "fas fa-fw fa-cloud-download-alt" > < / i >
2018-05-24 00:12:16 +08:00
< span > Update cache< / span >
2018-05-22 23:35:16 +08:00
< / a >
< a id = "cancel-edit" @ click = "toggleEditMode" >
< i class = "fas fa-fw fa-times" > < / i >
< / a >
< / div >
2018-06-07 12:15:58 +08:00
< div id = "grid" :class = "{list: options.listView}" >
2018-05-24 13:58:56 +08:00
< div class = "pagination-box" v-if = "maxPage > 0" >
< p > Page< / p >
< input type = "text" placeholder = "1" :value = "page+1" @ focus = "$event.target.select()" @ keyup . enter = "changePage($event.target.value-1)" :disabled = "editMode" >
< p > {{maxPage+1}}< / p >
< div class = "spacer" > < / div >
< template v-if = "!editMode" >
< a v-if = "page > 1" title = "Go to first page" @ click = "changePage(0)" >
< i class = "fas fa-fw fa-angle-double-left" > < / i >
< / a >
< a v-if = "page > 0" title = "Go to previous page" @ click = "changePage(page-1)" >
< i class = "fa fa-fw fa-angle-left" > < / i >
< / a >
< a v-if = "page < maxPage" title = "Go to next page" @ click = "changePage(page+1)" >
< i class = "fa fa-fw fa-angle-right" > < / i >
< / a >
< a v-if = "page < maxPage - 1" title = "Go to last page" @ click = "changePage(maxPage)" >
< i class = "fas fa-fw fa-angle-double-right" > < / i >
2018-05-21 17:37:48 +08:00
< / a >
2018-05-24 13:58:56 +08:00
< / template >
< / div >
< div class = "bookmark" v-for = "(book, idx) in visibleBookmarks" :class = "{selected: isSelected(idx)}" >
< a class = "bookmark-selector" v-if = "editMode" @ click = "toggleSelection(idx)" > < / a >
< a class = "bookmark-link" :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 >
2018-06-07 12:15:58 +08:00
< p v-show = "options.showBookmarkID" class = "id" > {{book.id}}< / p >
2018-05-24 13:58:56 +08:00
< / a >
< div class = "bookmark-tags" v-if = "book.tags.length > 0" >
< a v-for = "tag in book.tags" @ click = "filterTag(tag.name)" > {{tag.name}}< / a >
2018-05-21 17:37:48 +08:00
< / div >
2018-05-24 13:58:56 +08:00
< div class = "spacer" > < / div >
< div class = "bookmark-menu" >
< a class = "url" title = "View original" :href = "book.url" target = "_blank" >
{{getHostname(book.url)}}
< / a >
< a title = "Edit bookmark" @ click = "showDialogEdit(idx)" >
< i class = "fas fa-pencil-alt" > < / i >
< / a >
< a title = "Delete bookmark" @ click = "showDialogDelete([idx])" >
< i class = "fas fa-trash-alt" > < / i >
< / a >
< a title = "Update cache" @ click = "showDialogUpdateCache([idx])" >
< i class = "fas fa-cloud-download-alt" > < / i >
< / a >
2018-05-21 17:37:48 +08:00
< / div >
2018-03-10 11:39:38 +08:00
< / div >
2018-05-24 13:58:56 +08:00
< div class = "pagination-box" v-if = "maxPage > 0" >
< p > Page< / p >
< input type = "text" placeholder = "1" :value = "page+1" @ focus = "$event.target.select()" @ keyup . enter = "changePage($event.target.value-1)" :disabled = "editMode" >
< p > {{maxPage+1}}< / p >
< div class = "spacer" > < / div >
< template v-if = "!editMode" >
< a v-if = "page > 1" title = "Go to first page" @ click = "changePage(0)" >
< i class = "fas fa-fw fa-angle-double-left" > < / i >
< / a >
< a v-if = "page > 0" title = "Go to previous page" @ click = "changePage(page-1)" >
< i class = "fa fa-fw fa-angle-left" > < / i >
< / a >
< a v-if = "page < maxPage" title = "Go to next page" @ click = "changePage(page+1)" >
< i class = "fa fa-fw fa-angle-right" > < / i >
< / a >
< a v-if = "page < maxPage - 1" title = "Go to last page" @ click = "changePage(maxPage)" >
< i class = "fas fa-fw fa-angle-double-right" > < / i >
< / a >
< / template >
< / div >
< div id = "grid-padding" > < / div >
2018-03-10 11:39:38 +08:00
< / div >
< / div >
2018-06-14 11:32:51 +08:00
< yla-dialog id = "dialog-about" v-bind = "dialogAbout" >
< p >
Shiori is a simple bookmarks manager written in Go language, developed by
< a target = "_blank" href = "https://github.com/RadhiFadlillah" > Radhi Fadlillah< / a > and other
< a target = "_blank" href = "https://github.com/RadhiFadlillah/shiori/graphs/contributors" > contributors< / a > . The source code is available on
< a target = "_blank" href = "https://github.com/RadhiFadlillah/shiori" > GitHub< / a > and released under MIT license.
< / p >
< p > For ease of use, you can install the Shiori Bookmarklet by dragging this link (
< a :href = "bookmarklet" onclick = "event.preventDefault()" > +Shiori< / a > ) to your bookmark bar.
< / p >
< / yla-dialog >
2018-05-24 13:39:36 +08:00
< yla-dialog id = "dialog-tags" v-bind = "dialogTags" >
< a v-for = "tag in tags" @ click = "filterTag(tag.name)" >
< span > {{tag.name}}< / span >
< span > {{tag.nBookmarks}}< / span >
< / a >
< / yla-dialog >
2018-06-07 12:15:58 +08:00
< yla-dialog id = "dialog-options" v-bind = "dialogOptions" >
< a @ click = "toggleListView" >
< i class = "fa-fw" :class = "options.listView ? 'fas fa-check-square' : 'far fa-square'" > < / i > Use list view
< / a >
< a @ click = "toggleNightMode" >
< i class = "fa-fw" :class = "options.nightMode ? 'fas fa-check-square' : 'far fa-square'" > < / i > Use night mode
< / a >
< a @ click = "toggleBookmarkID" >
< i class = "fa-fw" :class = "options.showBookmarkID ? 'fas fa-check-square' : 'far fa-square'" > < / i > Show bookmark's ID
< / a >
< / yla-dialog >
2018-05-18 15:07:15 +08:00
< yla-dialog v-bind = "dialog" > < / yla-dialog >
2018-02-11 22:00:56 +08:00
< / div >
< script >
2018-05-21 14:28:08 +08:00
// Define global variable
var pageSize = 30;
2018-05-18 15:07:15 +08:00
// Prepare axios instance
2018-02-22 17:48:36 +08:00
var token = Cookies.get('token'),
2018-05-18 15:07:15 +08:00
rest = axios.create();
2018-02-22 17:48:36 +08:00
2018-05-18 17:18:38 +08:00
rest.defaults.timeout = 60000;
2018-05-18 15:07:15 +08:00
rest.defaults.headers.common['Authorization'] = 'Bearer ' + token;
2018-02-11 22:00:56 +08:00
2018-05-18 15:07:15 +08:00
// Register Vue component
Vue.component('yla-dialog', new YlaDialog());
Vue.component('yla-tooltip', new YlaTooltip());
new Vue({
el: '#index-page',
mixins: [new Base()],
2018-05-24 13:39:36 +08:00
data() {
return {
loading: false,
tags: [],
bookmarks: [],
search: '',
page: 0,
maxPage: 0,
editMode: false,
selected: [],
2018-06-06 22:48:46 +08:00
bookmarklet: '',
2018-06-07 12:15:58 +08:00
options: {
listView: false,
nightMode: false,
showBookmarkID: false,
},
2018-06-14 11:32:51 +08:00
dialogAbout: {
visible: false,
title: 'About',
mainClick: () => {
this.dialogAbout.visible = false;
},
},
2018-05-24 13:39:36 +08:00
dialogTags: {
visible: false,
loading: false,
title: 'Existing Tags',
mainText: 'Cancel',
mainClick: () => {
this.dialogTags.visible = false;
},
2018-06-07 12:15:58 +08:00
},
dialogOptions: {
visible: false,
title: 'Options',
mainText: 'OK',
mainClick: () => {
this.dialogOptions.visible = false;
},
2018-05-24 13:39:36 +08:00
}
}
2018-05-21 14:28:08 +08:00
},
computed: {
visibleBookmarks() {
var start = this.page * pageSize,
finish = start + pageSize;
return this.bookmarks.slice(start, finish);
}
2018-02-11 22:00:56 +08:00
},
methods: {
2018-05-18 15:07:15 +08:00
loadData() {
2018-02-24 15:01:52 +08:00
if (this.loading) return;
2018-05-21 15:03:08 +08:00
// Parse search query
2018-05-24 22:53:34 +08:00
var rxTagA = /['"]#([^'"]+)['"]/g,
rxTagB = /(^|\s+)#(\S+)/g,
keyword = this.search,
2018-05-21 15:03:08 +08:00
tags = [];
2018-05-24 22:53:34 +08:00
// Fetch tag A first
while ((result = rxTagA.exec(keyword)) !== null) {
tags.push(result[1]);
}
// Clear tag A from keyword
keyword = keyword.replace(rxTagA, '');
// Fetch tag B
while ((result = rxTagB.exec(keyword)) !== null) {
2018-05-21 15:03:08 +08:00
tags.push(result[2]);
}
2018-05-24 22:53:34 +08:00
// Clear tag B from keyword and clean it
keyword = keyword.replace(rxTagB, '').trim().replace(/\s+/g, ' ');
2018-02-23 15:30:58 +08:00
// Fetch data
2018-02-12 22:06:53 +08:00
this.loading = true;
2018-05-21 15:03:08 +08:00
rest.get('/api/bookmarks', {
params: {
keyword: keyword,
2018-05-24 22:53:34 +08:00
tags: tags.join(',')
2018-05-21 15:03:08 +08:00
}
})
2018-05-18 15:07:15 +08:00
.then((response) => {
2018-05-21 14:28:08 +08:00
this.page = 0;
2018-06-07 15:19:59 +08:00
this.bookmarks = response.data;
2018-05-21 14:28:08 +08:00
this.maxPage = Math.ceil(this.bookmarks.length / pageSize) - 1;
2018-05-24 00:12:16 +08:00
window.scrollTo(0, 0);
2018-06-07 15:19:59 +08:00
return rest.get('/api/tags');
})
.then((response) => {
this.tags = response.data;
this.loading = false;
2018-02-12 22:06:53 +08:00
})
2018-05-18 15:07:15 +08:00
.catch((error) => {
this.loading = false;
2018-06-07 16:37:07 +08:00
var errorMsg = (error.response ? error.response.data : error.message).trim();
if (errorMsg.startsWith('Token error')) this.showDialogSessionExpired(errorMsg);
else this.showErrorDialog(errorMsg);
2018-02-14 12:41:43 +08:00
});
},
2018-05-21 17:37:48 +08:00
reloadData() {
if (this.loading) return;
this.search = '';
this.loadData();
},
2018-05-21 14:28:08 +08:00
changePage(target) {
target = parseInt(target, 10) || 0;
if (target >= this.maxPage) this.page = this.maxPage;
else if (target < = 0) this.page = 0;
else this.page = target;
2018-05-24 00:12:16 +08:00
window.scrollTo(0, 0);
2018-05-21 17:37:48 +08:00
},
toggleListView() {
2018-06-07 12:15:58 +08:00
this.options.listView = !this.options.listView;
2018-05-24 00:12:16 +08:00
window.scrollTo(0, 0);
2018-06-07 12:15:58 +08:00
localStorage.setItem('shiori-list-view', this.options.listView ? '1' : '0');
2018-05-21 14:28:08 +08:00
},
2018-05-21 21:26:18 +08:00
toggleNightMode() {
2018-06-07 12:15:58 +08:00
this.options.nightMode = !this.options.nightMode;
localStorage.setItem('shiori-night-mode', this.options.nightMode ? '1' : '0');
},
toggleBookmarkID() {
this.options.showBookmarkID = !this.options.showBookmarkID;
localStorage.setItem('shiori-show-id', this.options.showBookmarkID ? '1' : '0');
2018-05-21 21:26:18 +08:00
},
2018-05-22 23:35:16 +08:00
toggleEditMode() {
this.editMode = !this.editMode;
this.selected = [];
},
toggleSelection(idx) {
var pos = this.selected.indexOf(idx);
if (pos === -1) this.selected.push(idx);
else this.selected.splice(pos, 1);
},
isSelected(idx) {
return this.selected.indexOf(idx) > -1;
},
2018-05-23 17:33:06 +08:00
filterTag(tag) {
2018-05-24 22:53:34 +08:00
// Prepare variable
var rxSpace = /\s+/g,
searchTag = rxSpace.test(tag) ? '"#' + tag + '"' : '#' + tag;
2018-05-23 17:33:06 +08:00
// Check if tag already exist in search
2018-05-24 22:53:34 +08:00
rxTag = new RegExp(searchTag, 'g');
2018-05-23 17:33:06 +08:00
if (rxTag.test(this.search)) return;
// Create new search query
var newSearch = this.search
.replace(rxTag, '')
2018-05-24 22:53:34 +08:00
.replace(rxSpace, ' ')
2018-05-23 17:33:06 +08:00
.trim();
// Load data
2018-05-24 22:53:34 +08:00
this.search = (newSearch + ' ' + searchTag).trim();
2018-05-24 13:39:36 +08:00
this.dialogTags.visible = false;
2018-05-23 17:33:06 +08:00
this.loadData();
},
2018-06-14 11:32:51 +08:00
showDialogAdd() {
2018-05-18 15:07:15 +08:00
this.showDialog({
title: 'New Bookmark',
2018-05-19 14:36:51 +08:00
content: 'Create a new bookmark',
2018-05-18 15:07:15 +08:00
fields: [{
name: 'url',
2018-05-19 14:36:51 +08:00
label: 'Url, start with http://...',
}, {
name: 'title',
label: 'Custom title (optional)'
}, {
name: 'excerpt',
label: 'Custom excerpt (optional)',
type: 'area'
2018-05-23 17:33:06 +08:00
}, {
name: 'tags',
2018-06-07 15:19:59 +08:00
label: 'Comma separated tags (optional)',
separator: ',',
dictionary: this.tags.map(tag => tag.name)
2018-05-23 17:33:06 +08:00
}, ],
2018-05-18 15:07:15 +08:00
mainText: 'OK',
secondText: 'Cancel',
mainClick: (data) => {
2018-05-19 14:36:51 +08:00
// Prepare tags
var tags = data.tags
.toLowerCase()
2018-05-24 22:53:34 +08:00
.replace(/\s+/g, ' ')
.split(/\s*,\s*/g)
2018-05-23 17:33:06 +08:00
.filter(tag => tag !== '')
.map(tag => {
2018-05-19 14:36:51 +08:00
return {
name: tag
};
});
// Send data
2018-05-18 15:07:15 +08:00
this.dialog.loading = true;
rest.post('/api/bookmarks', {
2018-05-19 14:36:51 +08:00
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.splice(0, 0, response.data);
})
.catch((error) => {
var errorMsg = (error.response ? error.response.data : error.message).trim();
2018-06-07 16:37:07 +08:00
if (errorMsg.startsWith('Token error')) this.showDialogSessionExpired(errorMsg);
else this.showErrorDialog(errorMsg);
2018-05-19 14:36:51 +08:00
});
}
});
},
2018-05-19 16:28:17 +08:00
showDialogEdit(idx) {
2018-05-28 16:54:59 +08:00
idx += this.page * pageSize;
2018-05-19 16:28:17 +08:00
var book = JSON.parse(JSON.stringify(this.bookmarks[idx])),
2018-05-26 17:44:53 +08:00
strTags = book.tags.map(tag => tag.name).join(', ');
2018-05-19 16:28:17 +08:00
this.showDialog({
title: 'Edit Bookmark',
content: 'Edit the bookmark\'s data',
showLabel: true,
fields: [{
name: 'title',
label: 'Title',
value: book.title,
}, {
name: 'excerpt',
label: 'Excerpt',
type: 'area',
value: book.excerpt,
2018-05-19 17:11:18 +08:00
}, {
name: 'tags',
label: 'Tags',
value: strTags,
2018-05-19 16:28:17 +08:00
}],
mainText: 'OK',
secondText: 'Cancel',
mainClick: (data) => {
// Validate input
if (data.title.trim() === '') return;
// Prepare tags
var tags = data.tags
.toLowerCase()
2018-05-24 22:53:34 +08:00
.replace(/\s+/g, ' ')
.split(/\s*,\s*/g)
2018-05-23 17:33:06 +08:00
.filter(tag => tag !== '')
.map(tag => {
2018-05-19 16:28:17 +08:00
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();
2018-06-07 16:37:07 +08:00
if (errorMsg.startsWith('Token error')) this.showDialogSessionExpired(errorMsg);
else this.showErrorDialog(errorMsg);
2018-05-19 16:28:17 +08:00
});
}
});
},
2018-05-19 14:36:51 +08:00
showDialogDelete(indices) {
// Check and prepare indices
if (!(indices instanceof Array)) return;
if (indices.length === 0) return;
indices.sort();
2018-05-28 16:54:59 +08:00
// Set real indices value
indices = indices.map(item => item + this.page * pageSize)
// Create title and content
2018-05-19 14:36:51 +08:00
var title = "Delete Bookmarks",
content = "Delete the selected bookmarks ? This action is irreversible.";
if (indices.length === 1) {
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 + + ) {
2018-05-19 23:43:15 +08:00
listID.push(this.bookmarks[indices[i]].id);
2018-05-19 14:36:51 +08:00
}
// Show dialog
this.showDialog({
title: title,
content: content,
mainText: 'Yes',
secondText: 'No',
mainClick: () => {
this.dialog.loading = true;
rest.delete('/api/bookmarks/', {
data: listID
2018-03-07 16:51:47 +08:00
})
2018-05-18 15:07:15 +08:00
.then((response) => {
2018-05-22 23:35:16 +08:00
this.selected = [];
this.editMode = false;
2018-05-18 15:07:15 +08:00
this.dialog.loading = false;
this.dialog.visible = false;
2018-05-19 14:36:51 +08:00
for (var i = indices.length - 1; i >= 0; i--) {
this.bookmarks.splice(indices[i], 1);
}
2018-03-07 16:51:47 +08:00
})
2018-05-18 15:07:15 +08:00
.catch((error) => {
var errorMsg = (error.response ? error.response.data : error.message).trim();
2018-06-07 16:37:07 +08:00
if (errorMsg.startsWith('Token error')) this.showDialogSessionExpired(errorMsg);
else this.showErrorDialog(errorMsg);
2018-03-07 16:51:47 +08:00
});
2018-05-18 15:07:15 +08:00
}
2018-02-13 17:14:08 +08:00
});
2018-05-18 15:07:15 +08:00
},
2018-05-19 17:11:18 +08:00
showDialogUpdateCache(indices) {
// Check and prepare indices
if (!(indices instanceof Array)) return;
if (indices.length === 0) return;
indices.sort();
2018-05-28 16:54:59 +08:00
// Set real indices value
indices = indices.map(item => item + this.page * pageSize)
2018-05-19 17:11:18 +08:00
// Get list of bookmark ID
var listID = [];
for (var i = 0; i < indices.length ; i + + ) {
2018-05-19 23:43:15 +08:00
listID.push(this.bookmarks[indices[i]].id);
2018-05-19 17:11:18 +08:00
}
// 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) => {
2018-05-22 23:35:16 +08:00
this.selected = [];
this.editMode = false;
2018-05-19 17:11:18 +08:00
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;
}
}
});
})
2018-05-23 17:33:06 +08:00
.catch((error) => {
var errorMsg = (error.response ? error.response.data : error.message).trim();
2018-06-07 16:37:07 +08:00
if (errorMsg.startsWith('Token error')) this.showDialogSessionExpired(errorMsg);
else this.showErrorDialog(errorMsg);
2018-05-23 17:33:06 +08:00
});
}
});
},
showDialogAddTags(indices) {
// Check and prepare indices
if (!(indices instanceof Array)) return;
if (indices.length === 0) return;
indices.sort();
2018-05-28 16:54:59 +08:00
// Set real indices value
indices = indices.map(item => item + this.page * pageSize)
2018-05-23 17:33:06 +08:00
// Get list of bookmark ID
var listID = [];
for (var i = 0; i < indices.length ; i + + ) {
listID.push(this.bookmarks[indices[i]].id);
}
this.showDialog({
title: 'Add New Tags',
content: 'Add new tags to selected bookmarks',
fields: [{
name: 'tags',
2018-05-24 22:53:34 +08:00
label: 'Comma separated tags',
2018-05-23 17:33:06 +08:00
value: '',
}],
mainText: 'OK',
secondText: 'Cancel',
mainClick: (data) => {
// Validate input
var tags = data.tags
.toLowerCase()
2018-05-24 22:53:34 +08:00
.replace(/\s+/g, ' ')
.split(/\s*,\s*/g)
2018-05-23 17:33:06 +08:00
.filter(tag => tag !== '')
.map(tag => {
return {
name: tag
};
});
if (tags.length === 0) return;
// Send data
this.dialog.loading = true;
rest.put('/api/bookmarks/tags', {
ids: listID,
tags: tags,
})
.then((response) => {
this.selected = [];
this.editMode = false;
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;
}
}
});
})
2018-05-19 17:11:18 +08:00
.catch((error) => {
var errorMsg = (error.response ? error.response.data : error.message).trim();
2018-06-07 16:37:07 +08:00
if (errorMsg.startsWith('Token error')) this.showDialogSessionExpired(errorMsg);
else this.showErrorDialog(errorMsg);
2018-05-19 17:11:18 +08:00
});
}
});
},
2018-05-24 13:39:36 +08:00
showDialogTags() {
this.dialogTags.visible = true;
this.dialogTags.loading = true;
rest.get('/api/tags', {
timeout: 5000
})
.then((response) => {
this.tags = response.data;
this.dialogTags.loading = false;
})
.catch((error) => {
this.dialogTags.loading = false;
this.dialogTags.visible = false;
2018-06-07 16:37:07 +08:00
var errorMsg = (error.response ? error.response.data : error.message).trim();
if (errorMsg.startsWith('Token error')) this.showDialogSessionExpired(errorMsg);
else this.showErrorDialog(errorMsg);
2018-05-24 13:39:36 +08:00
});
},
2018-05-23 21:14:26 +08:00
showDialogLogout() {
this.showDialog({
title: 'Log Out',
content: 'Do you want to log out from shiori ?',
mainText: 'Yes',
secondText: 'No',
mainClick: () => {
Cookies.remove('token');
location.href = '/login';
}
});
},
2018-06-07 16:37:07 +08:00
showDialogSessionExpired(msg) {
this.showDialog({
title: 'Error',
content: msg + '. Please login again.',
mainText: 'OK',
mainClick: () => {
Cookies.remove('token');
location.href = '/login';
}
});
},
2018-06-14 11:32:51 +08:00
showDialogAbout() {
this.dialogAbout.visible = true;
},
2018-06-07 12:15:58 +08:00
showDialogOptions() {
this.dialogOptions.visible = true;
},
2018-05-18 15:07:15 +08:00
getHostname(url) {
parser = document.createElement('a');
parser.href = url;
2018-05-19 14:36:51 +08:00
return parser.hostname.replace(/^www\./g, '');
2018-02-13 17:14:08 +08:00
}
},
2018-05-18 15:07:15 +08:00
mounted() {
2018-05-21 17:37:48 +08:00
// Read config from local storage
var listView = localStorage.getItem('shiori-list-view'),
2018-06-07 12:15:58 +08:00
nightMode = localStorage.getItem('shiori-night-mode'),
showBookmarkID = localStorage.getItem('shiori-show-id');
2018-05-21 17:37:48 +08:00
2018-06-07 12:15:58 +08:00
this.options.listView = listView === '1';
this.options.nightMode = nightMode === '1';
this.options.showBookmarkID = showBookmarkID === '1';
2018-05-21 17:37:48 +08:00
2018-06-06 22:48:46 +08:00
// Create bookmarklet
var shioriURL = location.href.replace(/\/+$/g, ''),
baseBookmarklet = `(function () {
var shioriURL = '$SHIORI_URL',
bookmarkURL = location.href,
submitURL = shioriURL + '/submit?url=' + encodeURIComponent(bookmarkURL);
if (bookmarkURL.startsWith('https://') & & !shioriURL.startsWith('https://')) {
window.open(submitURL, '_blank');
return;
}
var i = document.createElement('iframe');
i.src = submitURL;
i.frameBorder = '0';
i.allowTransparency = true;
i.style.position = 'fixed';
i.style.top = 0;
i.style.left = 0;
i.style.width = '100%';
i.style.height = '100%';
i.style.zIndex = 99999;
document.body.appendChild(i);
window.addEventListener('message', function onMessage(e) {
if (e.origin !== shioriURL) return;
if (e.data !== 'finished') return;
window.removeEventListener('message', onMessage);
document.body.removeChild(i);
});
}())`;
this.bookmarklet = 'javascript:' + baseBookmarklet
.replace('$SHIORI_URL', shioriURL)
.replace(/\s+/gm, ' ');
2018-05-21 17:37:48 +08:00
// Load data
2018-02-11 22:00:56 +08:00
this.loadData();
}
2018-05-18 15:07:15 +08:00
});
2018-02-11 22:00:56 +08:00
< / script >
< / body >
2018-03-10 11:39:38 +08:00
< / html >