mirror of
https://github.com/go-shiori/shiori.git
synced 2025-12-09 05:25:53 +08:00
commit
eee3dc75c2
9 changed files with 187 additions and 19 deletions
6
assets/assets.go
Executable file → Normal file
6
assets/assets.go
Executable file → Normal file
File diff suppressed because one or more lines are too long
15
cmd/serve.go
15
cmd/serve.go
|
|
@ -68,6 +68,7 @@ var (
|
|||
|
||||
router.POST("/api/login", apiLogin)
|
||||
router.GET("/api/bookmarks", apiGetBookmarks)
|
||||
router.GET("/api/tags", apiGetTags)
|
||||
router.POST("/api/bookmarks", apiInsertBookmarks)
|
||||
router.PUT("/api/bookmarks", apiUpdateBookmarks)
|
||||
router.DELETE("/api/bookmarks", apiDeleteBookmarks)
|
||||
|
|
@ -222,6 +223,20 @@ func apiGetBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Param
|
|||
checkError(err)
|
||||
}
|
||||
|
||||
func apiGetTags(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
|
||||
// Check token
|
||||
err := checkAPIToken(r)
|
||||
checkError(err)
|
||||
|
||||
// Fetch all tags
|
||||
tags, err := DB.GetTags()
|
||||
checkError(err)
|
||||
|
||||
err = json.NewEncoder(w).Encode(&tags)
|
||||
checkError(err)
|
||||
}
|
||||
|
||||
func apiInsertBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||
// Check token
|
||||
err := checkAPIToken(r)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,9 @@ type Database interface {
|
|||
// GetBookmarks fetch list of bookmarks based on submitted indices.
|
||||
GetBookmarks(withContent bool, indices ...string) ([]model.Bookmark, error)
|
||||
|
||||
//GetTags fetch list of tags and their frequency
|
||||
GetTags() ([]model.Tag, error)
|
||||
|
||||
// DeleteBookmarks removes all record with matching indices from database.
|
||||
DeleteBookmarks(indices ...string) error
|
||||
|
||||
|
|
|
|||
|
|
@ -565,3 +565,19 @@ func (db *SQLiteDatabase) DeleteAccounts(usernames ...string) error {
|
|||
_, err := db.Exec(`DELETE FROM account `+whereClause, args...)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetTags fetch list of tags and their frequency
|
||||
func (db *SQLiteDatabase) GetTags() ([]model.Tag, error) {
|
||||
tags := []model.Tag{}
|
||||
query := `SELECT bt.tag_id id, t.name, COUNT(bt.tag_id) n_bookmarks
|
||||
FROM bookmark_tag bt
|
||||
LEFT JOIN tag t ON bt.tag_id = t.id
|
||||
GROUP BY bt.tag_id ORDER BY t.name`
|
||||
|
||||
err := db.Select(&tags, query)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@ package model
|
|||
|
||||
// Tag is tag for the bookmark
|
||||
type Tag struct {
|
||||
ID int64 `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
Deleted bool `json:"-"`
|
||||
ID int64 `db:"id" json:"id"`
|
||||
Name string `db:"name" json:"name"`
|
||||
NBookmarks int64 `db:"n_bookmarks" json:"nBookmarks"`
|
||||
Deleted bool `json:"-"`
|
||||
}
|
||||
|
||||
// Bookmark is record of a specified URL
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -35,6 +35,10 @@
|
|||
<i class="fas fa-cloud fa-fw"></i>
|
||||
<span>Reload</span>
|
||||
</a>
|
||||
<a @click="showTagCloud">
|
||||
<i class="fas fa-hashtag fa-fw"></i>
|
||||
<span>Tags</span>
|
||||
</a>
|
||||
<a @click="toggleImage">
|
||||
<i class="fas fa-fw" :class="showImage ? 'fa-eye-slash' : 'fa-eye'"></i>
|
||||
<span>{{showImage ? 'Hide image' : 'Show image'}}</span>
|
||||
|
|
@ -147,6 +151,19 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="tagCloud.visible" id="tag-cloud-overlay">
|
||||
<div id="tag-cloud">
|
||||
<div id="tag-cloud-title">
|
||||
<p>Tag Cloud</p>
|
||||
<a @click="tagCloud.visible = false" id="close-tag-cloud">
|
||||
<i class="fas fa-fw fa-times"></i>
|
||||
</a>
|
||||
</div>
|
||||
<div id="tag-cloud-content">
|
||||
<a v-for="item in tagCloud.data" @click="selectTagCloud(item.name)" :style="{fontSize: item.fontSize+'em'}">#{{item.name}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var token = Cookies.get('token'),
|
||||
|
|
@ -161,7 +178,9 @@
|
|||
windowWidth: 0,
|
||||
error: "",
|
||||
loading: false,
|
||||
displayTags: false,
|
||||
bookmarks: [],
|
||||
tags: [],
|
||||
checkedBookmarks: [],
|
||||
showImage: true,
|
||||
search: {
|
||||
|
|
@ -189,6 +208,10 @@
|
|||
secondChoice: '',
|
||||
mainAction: function () {},
|
||||
secondAction: function () {}
|
||||
},
|
||||
tagCloud: {
|
||||
visible: false,
|
||||
data: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -471,6 +494,53 @@
|
|||
bookmarkItem[0].scrollIntoView();
|
||||
})
|
||||
},
|
||||
showTagCloud: function () {
|
||||
if (this.loading) return;
|
||||
|
||||
// Fetch data
|
||||
this.error = '';
|
||||
this.loading = true;
|
||||
instance.get('/api/tags')
|
||||
.then(function (response) {
|
||||
app.loading = false;
|
||||
app.tagCloud.data = response.data;
|
||||
|
||||
if (app.tagCloud.data.length === 0) {
|
||||
app.tagCloud.visible = false;
|
||||
app.showDialogError("Error Creating Tag Cloud", "There are no saved tags");
|
||||
return;
|
||||
}
|
||||
|
||||
app.tagCloud.visible = true;
|
||||
|
||||
// Find largest tags frequency
|
||||
var minFont = 1,
|
||||
maxFont = 5,
|
||||
maxCount = 0;
|
||||
for (var i = 0; i < app.tagCloud.data.length; i++) {
|
||||
if (app.tagCloud.data[i].nBookmarks > maxCount) {
|
||||
maxCount = app.tagCloud.data[i].nBookmarks;
|
||||
}
|
||||
}
|
||||
|
||||
// Set font size for tags
|
||||
for (var i = 0; i < app.tagCloud.data.length; i++) {
|
||||
var size = (Math.log(app.tagCloud.data[i].nBookmarks) / Math.log(maxCount)) *
|
||||
(maxFont - minFont) + minFont;
|
||||
app.tagCloud.data[i].fontSize = size;
|
||||
}
|
||||
})
|
||||
.catch(function (error) {
|
||||
var errorMsg = error.response ? error.response.data : error.message;
|
||||
app.loading = false;
|
||||
app.tagCloud.visible = false;
|
||||
app.showDialogError("Error Creating Tag Cloud", errorMsg.trim());
|
||||
});
|
||||
},
|
||||
selectTagCloud: function (tag) {
|
||||
this.tagCloud.visible = false;
|
||||
this.searchTag(tag);
|
||||
},
|
||||
bookmarkTime: function (book) {
|
||||
// Define read time
|
||||
var readTime = "";
|
||||
|
|
@ -543,6 +613,9 @@
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
overlayVisible: function () {
|
||||
return this.dialog.visible || this.tagCloud.visible;
|
||||
},
|
||||
gridColumns: function () {
|
||||
var nColumn = Math.round(this.windowWidth / 500),
|
||||
finalContent = [],
|
||||
|
|
@ -571,6 +644,10 @@
|
|||
}
|
||||
},
|
||||
watch: {
|
||||
overlayVisible: function (isVisible) {
|
||||
if (isVisible) document.body.className = "noscroll";
|
||||
else document.body.removeAttribute("class");
|
||||
},
|
||||
'inputBookmark.url': function (newURL) {
|
||||
if (newURL === "") this.clearInputBookmark();
|
||||
else this.$nextTick(function () {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
flex: 1 0;
|
||||
}
|
||||
|
||||
.noscroll {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#login-page {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
|
|
@ -437,18 +441,7 @@
|
|||
}
|
||||
|
||||
#dialog-overlay {
|
||||
position: fixed;
|
||||
z-index: 101;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
justify-content: center;
|
||||
padding: 32px;
|
||||
.full-overlay();
|
||||
#dialog {
|
||||
display: flex;
|
||||
background-color: @contentBg;
|
||||
|
|
@ -491,6 +484,54 @@
|
|||
}
|
||||
}
|
||||
|
||||
#tag-cloud-overlay {
|
||||
.full-overlay();
|
||||
#tag-cloud {
|
||||
display: flex;
|
||||
background-color: @contentBg;
|
||||
flex-flow: column nowrap;
|
||||
border: 1px solid @border;
|
||||
max-height: 100%;
|
||||
#tag-cloud-title {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid @border;
|
||||
align-items: center;
|
||||
p {
|
||||
color: @fontColor;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
font-size: 1em;
|
||||
flex: 1 0;
|
||||
}
|
||||
a {
|
||||
color: @fontLightColor;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
color: @main;
|
||||
}
|
||||
}
|
||||
}
|
||||
#tag-cloud-content {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
a {
|
||||
color: @fontLightColor;
|
||||
cursor: pointer;
|
||||
margin: 4px;
|
||||
&:hover {
|
||||
color: @main;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: @main !important;
|
||||
font-size: 0.9em;
|
||||
|
|
|
|||
|
|
@ -24,4 +24,19 @@
|
|||
&:hover {
|
||||
color: @main;
|
||||
}
|
||||
}
|
||||
|
||||
.full-overlay {
|
||||
position: fixed;
|
||||
z-index: 101;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
justify-content: center;
|
||||
padding: 32px;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue