mirror of
https://github.com/go-shiori/shiori.git
synced 2025-02-22 15:06:04 +08:00
Implement ad bookmark in web interface
This commit is contained in:
parent
452fa6bed1
commit
59208fb377
7 changed files with 234 additions and 133 deletions
12
cmd/add.go
12
cmd/add.go
|
@ -22,11 +22,13 @@ var (
|
||||||
offline, _ := cmd.Flags().GetBool("offline")
|
offline, _ := cmd.Flags().GetBool("offline")
|
||||||
|
|
||||||
// Save new bookmark
|
// Save new bookmark
|
||||||
err := addBookmark(url, title, excerpt, tags, offline)
|
bookmark, err := addBookmark(url, title, excerpt, tags, offline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cError.Println(err)
|
cError.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printBookmark(bookmark)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -39,7 +41,7 @@ func init() {
|
||||||
rootCmd.AddCommand(addCmd)
|
rootCmd.AddCommand(addCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addBookmark(url, title, excerpt string, tags []string, offline bool) (err error) {
|
func addBookmark(url, title, excerpt string, tags []string, offline bool) (book model.Bookmark, err error) {
|
||||||
// Fetch data from internet
|
// Fetch data from internet
|
||||||
article := readability.Article{}
|
article := readability.Article{}
|
||||||
if !offline {
|
if !offline {
|
||||||
|
@ -83,10 +85,8 @@ func addBookmark(url, title, excerpt string, tags []string, offline bool) (err e
|
||||||
// Save to database
|
// Save to database
|
||||||
bookmark.ID, err = DB.SaveBookmark(bookmark)
|
bookmark.ID, err = DB.SaveBookmark(bookmark)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return book, err
|
||||||
}
|
}
|
||||||
|
|
||||||
printBookmark(bookmark)
|
return bookmark, nil
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
47
cmd/serve.go
47
cmd/serve.go
|
@ -4,12 +4,12 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
db "github.com/RadhiFadlillah/shiori/database"
|
db "github.com/RadhiFadlillah/shiori/database"
|
||||||
|
"github.com/RadhiFadlillah/shiori/model"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"net/http"
|
"net/http"
|
||||||
fp "path/filepath"
|
fp "path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -27,6 +27,12 @@ var (
|
||||||
router.GET("/css/*filepath", serveFiles)
|
router.GET("/css/*filepath", serveFiles)
|
||||||
router.GET("/webfonts/*filepath", serveFiles)
|
router.GET("/webfonts/*filepath", serveFiles)
|
||||||
router.GET("/api/bookmarks", apiGetBookmarks)
|
router.GET("/api/bookmarks", apiGetBookmarks)
|
||||||
|
router.POST("/api/bookmarks", apiInsertBookmarks)
|
||||||
|
|
||||||
|
// Route for panic
|
||||||
|
router.PanicHandler = func(w http.ResponseWriter, r *http.Request, arg interface{}) {
|
||||||
|
http.Error(w, fmt.Sprint(arg), 500)
|
||||||
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf(":%d", 8080)
|
url := fmt.Sprintf(":%d", 8080)
|
||||||
logrus.Infoln("Serve shiori in", url)
|
logrus.Infoln("Serve shiori in", url)
|
||||||
|
@ -48,26 +54,29 @@ func serveFiles(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiGetBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
func apiGetBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
queries := r.URL.Query()
|
bookmarks, err := DB.GetBookmarks(db.GetBookmarksOptions{OrderLatest: true})
|
||||||
strLimit := queries.Get("limit")
|
|
||||||
strOffset := queries.Get("offset")
|
|
||||||
|
|
||||||
limit, _ := strconv.Atoi(strLimit)
|
|
||||||
if limit <= 0 {
|
|
||||||
limit = 20
|
|
||||||
}
|
|
||||||
|
|
||||||
offset, _ := strconv.Atoi(strOffset)
|
|
||||||
if offset <= 0 {
|
|
||||||
offset = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmarks, err := DB.GetBookmarks(db.GetBookmarksOptions{
|
|
||||||
Limit: limit,
|
|
||||||
Offset: offset,
|
|
||||||
OrderLatest: true})
|
|
||||||
checkError(err)
|
checkError(err)
|
||||||
|
|
||||||
err = json.NewEncoder(w).Encode(&bookmarks)
|
err = json.NewEncoder(w).Encode(&bookmarks)
|
||||||
checkError(err)
|
checkError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiInsertBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
||||||
|
// Decode request
|
||||||
|
request := model.Bookmark{}
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&request)
|
||||||
|
checkError(err)
|
||||||
|
|
||||||
|
// Save bookmark
|
||||||
|
tags := make([]string, len(request.Tags))
|
||||||
|
for i, tag := range request.Tags {
|
||||||
|
tags[i] = tag.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
book, err := addBookmark(request.URL, request.Title, request.Excerpt, tags, false)
|
||||||
|
checkError(err)
|
||||||
|
|
||||||
|
// Return new saved result
|
||||||
|
err = json.NewEncoder(w).Encode(&book)
|
||||||
|
checkError(err)
|
||||||
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@ type Database interface {
|
||||||
type GetBookmarksOptions struct {
|
type GetBookmarksOptions struct {
|
||||||
WithContents bool
|
WithContents bool
|
||||||
OrderLatest bool
|
OrderLatest bool
|
||||||
Limit int
|
|
||||||
Offset int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkError(err error) {
|
func checkError(err error) {
|
||||||
|
|
|
@ -216,10 +216,6 @@ func (db *SQLiteDatabase) GetBookmarks(options GetBookmarksOptions, indices ...s
|
||||||
query += ` ORDER BY modified DESC`
|
query += ` ORDER BY modified DESC`
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.Limit > 0 {
|
|
||||||
query += fmt.Sprintf(` LIMIT %d OFFSET %d`, options.Limit, options.Offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
bookmarks := []model.Bookmark{}
|
bookmarks := []model.Bookmark{}
|
||||||
err := db.Select(&bookmarks, query, args...)
|
err := db.Select(&bookmarks, query, args...)
|
||||||
if err != nil && err != sql.ErrNoRows {
|
if err != nil && err != sql.ErrNoRows {
|
||||||
|
|
File diff suppressed because one or more lines are too long
142
view/index.html
142
view/index.html
|
@ -20,58 +20,61 @@
|
||||||
<span>栞</span>shiori</a>
|
<span>栞</span>shiori</a>
|
||||||
<div id="search-box">
|
<div id="search-box">
|
||||||
<input type="text" name="keyword" id="input-search" placeholder="Search tags, title or content">
|
<input type="text" name="keyword" id="input-search" placeholder="Search tags, title or content">
|
||||||
<button type="submit">
|
<a class="button">
|
||||||
<i class="fas fa-search fa-fw"></i>
|
<i class="fas fa-search fa-fw"></i>
|
||||||
</button>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="main">
|
<div id="main">
|
||||||
<div id="new-bookmark">
|
<div id="new-bookmark">
|
||||||
<input type="text" name="url" id="input-new-bookmark" placeholder="URL for new bookmark">
|
<input type="text" v-model="newBookmark.url" placeholder="URL for the bookmark">
|
||||||
<button type="submit">
|
<template v-if="newBookmark.url !== ''">
|
||||||
<i class="fas fa-fw fa-cloud-download-alt" aria-hidden="true"></i>
|
<input type="text" v-model="newBookmark.title" placeholder="Custom bookmark title (optional)">
|
||||||
</button>
|
<input type="text" v-model="newBookmark.tags" placeholder="Space separated tags for this bookmark (optional)">
|
||||||
|
<textarea name="excerpt" v-model="newBookmark.excerpt" placeholder="Excerpt for this bookmark (optional)"></textarea>
|
||||||
|
<div class="button-area">
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<a v-if="newBookmark.loading">
|
||||||
|
<i class="fas fa-fw fa-spinner fa-spin"></i>
|
||||||
|
</a>
|
||||||
|
<template v-else>
|
||||||
|
<a class="button">Cancel</a>
|
||||||
|
<a class="button" @click="saveBookmark">Done</a>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div id="grid">
|
<div id="grid">
|
||||||
<div v-for="column in gridColumns" class="column">
|
<div v-for="column in gridColumns" class="column">
|
||||||
<div v-for="item in column" class="bookmark">
|
<div v-for="item in column" class="bookmark">
|
||||||
<a href="#" class="checkbox"></a>
|
<a href="#" class="checkbox">
|
||||||
<a v-if="item.imageURL !== ''" class="bookmark-image" :href="item.url" :style="bookmarkImage(item)">
|
<i class="fas fa-check"></i>
|
||||||
<div class="bookmark-metadata">
|
|
||||||
<p class="bookmark-time">{{bookmarkTime(item)}}</p>
|
|
||||||
<p class="bookmark-title">{{item.title}}</p>
|
|
||||||
<p class="bookmark-url">{{item.url}}</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
</a>
|
||||||
<a v-else class="bookmark-metadata" :href="item.url">
|
<a class="bookmark-metadata" :class="{'has-image':item.imageURL !== ''}" :style="bookmarkImage(item)" :href="item.url">
|
||||||
<p class="bookmark-time">{{bookmarkTime(item)}}</p>
|
<p class="bookmark-time">{{bookmarkTime(item)}}</p>
|
||||||
<p class="bookmark-title">{{item.title}}</p>
|
<p class="bookmark-title">{{item.title}}</p>
|
||||||
<p class="bookmark-url">{{item.url}}</p>
|
<p class="bookmark-url">{{getDomainURL(item.url)}}</p>
|
||||||
</a>
|
</a>
|
||||||
<p v-if="item.excerpt !== ''" class="bookmark-excerpt">{{item.excerpt}}</p>
|
<p v-if="item.excerpt !== ''" class="bookmark-excerpt">{{item.excerpt}}</p>
|
||||||
<div v-if="item.tags.length > 0" class="bookmark-tags">
|
<div v-if="item.tags.length > 0" class="bookmark-tags">
|
||||||
<a v-for="tag in item.tags" href="#">{{tag.name}}</a>
|
<a v-for="tag in item.tags" href="#">{{tag.name}}</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="bookmark-menu">
|
||||||
|
<a href="#">
|
||||||
|
<i class="fas fa-pencil-alt"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#">
|
||||||
|
<i class="far fa-trash-alt"></i>
|
||||||
|
</a>
|
||||||
|
<a href="#">
|
||||||
|
<i class="fas fa-history"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="bookmark checked">
|
|
||||||
<a href="#" class="checkbox"></a>
|
|
||||||
<a class="bookmark-image" href="https://www.nytimes.com/2018/01/26/reader-center/arctic-winter-night.html" style="background-image: url(https://cdn-images-1.medium.com/max/1200/1*NDXd5I87VZG0Z74N7dog0g.png)">
|
|
||||||
<div class="bookmark-metadata">
|
|
||||||
<p class="bookmark-time">2017-12-20 · 8-12 min read</p>
|
|
||||||
<p class="bookmark-title">Snapshots From a Land of Endless Night</p>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
<p class="bookmark-excerpt">Readers describe the thrills and challenges of wintertime in the Far North.</p>
|
|
||||||
<div class="bookmark-tags">
|
|
||||||
<a href="#">amazing</a>
|
|
||||||
<a href="#">nature</a>
|
|
||||||
<a href="#">nature</a>
|
|
||||||
</div>
|
|
||||||
</div> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div id="progress-bar">
|
<div id="progress-bar">
|
||||||
<i v-if="isLoading" class="fas fa-fw fa-spinner fa-spin"></i>
|
<i v-if="loading" class="fas fa-fw fa-spinner fa-spin"></i>
|
||||||
<a href="#">Load more bookmarks</a>
|
<a href="#">Load more bookmarks</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -84,22 +87,69 @@
|
||||||
el: '#app',
|
el: '#app',
|
||||||
data: {
|
data: {
|
||||||
windowWidth: 0,
|
windowWidth: 0,
|
||||||
isLoading: true,
|
loading: true,
|
||||||
bookmarks: []
|
bookmarks: [],
|
||||||
|
newBookmark: {
|
||||||
|
url: "",
|
||||||
|
title: "",
|
||||||
|
tags: "",
|
||||||
|
excerpt: "",
|
||||||
|
loading: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
loadData: function () {
|
loadData: function () {
|
||||||
this.isLoading = true;
|
this.loading = true;
|
||||||
instance.get('/api/bookmarks')
|
instance.get('/api/bookmarks')
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
app.isLoading = false;
|
app.loading = false;
|
||||||
app.bookmarks = response.data;
|
app.bookmarks = response.data;
|
||||||
console.log(JSON.stringify(app.gridColumns, "", " "));
|
|
||||||
})
|
})
|
||||||
.catch(function (error) {
|
.catch(function (error) {
|
||||||
|
app.loading = false;
|
||||||
console.log(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
saveBookmark: function () {
|
||||||
|
if (this.newBookmark.loading) return;
|
||||||
|
this.newBookmark.loading = true;
|
||||||
|
|
||||||
|
if (this.newBookmark.url === "") return;
|
||||||
|
|
||||||
|
var tags = this.newBookmark.tags.replace(/\s+/g, ""),
|
||||||
|
listTag = tags === "" ? [] : listTag = tags.split(/\s+/g),
|
||||||
|
finalTag = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < listTag.length; i++) {
|
||||||
|
finalTag.push({
|
||||||
|
name: listTag[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.post('/api/bookmarks', {
|
||||||
|
url: this.newBookmark.url,
|
||||||
|
title: this.newBookmark.title,
|
||||||
|
excerpt: this.newBookmark.excerpt,
|
||||||
|
tags: finalTag
|
||||||
|
}, {
|
||||||
|
timeout: 15000
|
||||||
|
})
|
||||||
|
.then(function (response) {
|
||||||
|
app.clearNewBookmark();
|
||||||
|
app.bookmarks.unshift(response.data);
|
||||||
|
})
|
||||||
|
.catch(function (error) {
|
||||||
|
app.clearNewBookmark();
|
||||||
|
console.log(error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
clearNewBookmark: function () {
|
||||||
|
this.newBookmark.url = "";
|
||||||
|
this.newBookmark.title = "";
|
||||||
|
this.newBookmark.tags = "";
|
||||||
|
this.newBookmark.excerpt = "";
|
||||||
|
this.newBookmark.loading = false;
|
||||||
|
},
|
||||||
bookmarkTime: function (book) {
|
bookmarkTime: function (book) {
|
||||||
var time = book.modified,
|
var time = book.modified,
|
||||||
readTime = "",
|
readTime = "",
|
||||||
|
@ -114,13 +164,27 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
finalBookmarkTime = "Updated " + time;
|
finalBookmarkTime = "Updated " + time;
|
||||||
if (readTime !== "") finalBookmarkTime += "\u00B7 " + readTime;
|
if (readTime !== "") finalBookmarkTime += " \u00B7 " + readTime;
|
||||||
|
|
||||||
return finalBookmarkTime;
|
return finalBookmarkTime;
|
||||||
},
|
},
|
||||||
bookmarkImage: function (book) {
|
bookmarkImage: function (book) {
|
||||||
var newURL = "background-image: url(" + book.imageURL + ")";
|
if (book.imageURL === "") return "";
|
||||||
return newURL;
|
return "background-image: url(" + book.imageURL + ")";
|
||||||
|
},
|
||||||
|
getDomainURL: function (url) {
|
||||||
|
var hostname;
|
||||||
|
|
||||||
|
if (url.indexOf("://") > -1) {
|
||||||
|
hostname = url.split('/')[2];
|
||||||
|
} else {
|
||||||
|
hostname = url.split('/')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname = hostname.split(':')[0];
|
||||||
|
hostname = hostname.split('?')[0];
|
||||||
|
|
||||||
|
return hostname;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spacer {
|
||||||
|
flex: 1 0;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
background-color: @appBg;
|
background-color: @appBg;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -50,7 +54,7 @@
|
||||||
flex-flow: row nowrap;
|
flex-flow: row nowrap;
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
button,
|
.button,
|
||||||
input {
|
input {
|
||||||
background-color: @headerInputBg;
|
background-color: @headerInputBg;
|
||||||
border: 1px solid @border;
|
border: 1px solid @border;
|
||||||
|
@ -58,7 +62,7 @@
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
button {
|
.button {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: @linkColor;
|
color: @linkColor;
|
||||||
&:hover {
|
&:hover {
|
||||||
|
@ -81,28 +85,40 @@
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row nowrap;
|
flex-flow: column nowrap;
|
||||||
margin: 32px 8px 20px;
|
margin: 32px 8px 20px;
|
||||||
button,
|
background-color: @headerInputBg;
|
||||||
input {
|
outline: 1px solid @border;
|
||||||
background-color: @headerInputBg;
|
input[type=text],
|
||||||
border: 1px solid @border;
|
textarea {
|
||||||
|
outline: 1px solid @border;
|
||||||
color: @fontColor;
|
color: @fontColor;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 4em;
|
||||||
|
max-height: 10em;
|
||||||
|
}
|
||||||
|
.button-area {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
a {
|
||||||
button {
|
color: @linkColor;
|
||||||
cursor: pointer;
|
text-transform: uppercase;
|
||||||
color: @linkColor;
|
padding: 8px;
|
||||||
&:hover {
|
background-color: @headerInputBg;
|
||||||
color: @accent;
|
&.button {
|
||||||
|
font-size: 0.9em;
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
color: @accent;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input[type=text] {
|
|
||||||
border-right: 0;
|
|
||||||
flex: 1 0;
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#grid {
|
#grid {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -146,55 +162,17 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
outline: 1px solid @border;
|
outline: 1px solid @border;
|
||||||
color: @fontLightColor;
|
color: @linkColor;
|
||||||
background-color: @headerBg;
|
background-color: @headerBg;
|
||||||
&::before {
|
width: 32px;
|
||||||
width: 32px;
|
line-height: 32px;
|
||||||
line-height: 32px;
|
text-align: center;
|
||||||
text-align: center;
|
display: block;
|
||||||
display: block;
|
font-size: 0.9em;
|
||||||
font-family: "Font Awesome 5 Free";
|
|
||||||
font-weight: 900;
|
|
||||||
font-size: 0.9em;
|
|
||||||
content: "\f00c";
|
|
||||||
}
|
|
||||||
&:hover {
|
&:hover {
|
||||||
color: @accent !important;
|
color: @accent !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bookmark-image {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
height: 250px;
|
|
||||||
background-position: center;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: cover;
|
|
||||||
.bookmark-metadata {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
padding: 16px;
|
|
||||||
justify-content: flex-end;
|
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
|
||||||
border-bottom: 0;
|
|
||||||
.bookmark-time,
|
|
||||||
.bookmark-url {
|
|
||||||
color: white;
|
|
||||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
.bookmark-title {
|
|
||||||
color: white;
|
|
||||||
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
.bookmark-title {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bookmark-metadata {
|
.bookmark-metadata {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -213,6 +191,39 @@
|
||||||
.bookmark-time;
|
.bookmark-time;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
max-height: 2.6em;
|
||||||
|
line-height: 1.3em;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
&.has-image {
|
||||||
|
min-height: 250px;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
justify-content: flex-end;
|
||||||
|
position: relative;
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
.bookmark-time,
|
||||||
|
.bookmark-url {
|
||||||
|
z-index: 2;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
.bookmark-title {
|
||||||
|
z-index: 2;
|
||||||
|
color: white;
|
||||||
|
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
.bookmark-title {
|
.bookmark-title {
|
||||||
|
@ -242,10 +253,33 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.bookmark-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
border-top: 1px solid @border;
|
||||||
|
visibility: hidden;
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
flex: 1 0;
|
||||||
|
color: @linkColor !important;
|
||||||
|
padding: 8px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
text-align: center;
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-right: 1px solid @border;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
color: @accent !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
.checkbox {
|
.checkbox {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
.bookmark-menu {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
&.checked {
|
&.checked {
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
Loading…
Reference in a new issue