From 59208fb37781fbed464b9e966ee67d28eb76206d Mon Sep 17 00:00:00 2001 From: Radhi Fadlillah Date: Mon, 12 Feb 2018 21:06:53 +0700 Subject: [PATCH] Implement ad bookmark in web interface --- cmd/add.go | 12 +-- cmd/serve.go | 47 +++++++----- database/database.go | 2 - database/sqlite.go | 4 - view/css/stylesheet.css | 2 +- view/index.html | 142 ++++++++++++++++++++++++---------- view/less/stylesheet.less | 158 +++++++++++++++++++++++--------------- 7 files changed, 234 insertions(+), 133 deletions(-) diff --git a/cmd/add.go b/cmd/add.go index 7611340..1c98192 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -22,11 +22,13 @@ var ( offline, _ := cmd.Flags().GetBool("offline") // Save new bookmark - err := addBookmark(url, title, excerpt, tags, offline) + bookmark, err := addBookmark(url, title, excerpt, tags, offline) if err != nil { cError.Println(err) os.Exit(1) } + + printBookmark(bookmark) }, } ) @@ -39,7 +41,7 @@ func init() { 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 article := readability.Article{} if !offline { @@ -83,10 +85,8 @@ func addBookmark(url, title, excerpt string, tags []string, offline bool) (err e // Save to database bookmark.ID, err = DB.SaveBookmark(bookmark) if err != nil { - return err + return book, err } - printBookmark(bookmark) - - return nil + return bookmark, nil } diff --git a/cmd/serve.go b/cmd/serve.go index a253bae..506c7f0 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -4,12 +4,12 @@ import ( "encoding/json" "fmt" db "github.com/RadhiFadlillah/shiori/database" + "github.com/RadhiFadlillah/shiori/model" "github.com/julienschmidt/httprouter" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "net/http" fp "path/filepath" - "strconv" "strings" ) @@ -27,6 +27,12 @@ var ( router.GET("/css/*filepath", serveFiles) router.GET("/webfonts/*filepath", serveFiles) 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) 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) { - queries := r.URL.Query() - 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}) + bookmarks, err := DB.GetBookmarks(db.GetBookmarksOptions{OrderLatest: true}) checkError(err) err = json.NewEncoder(w).Encode(&bookmarks) 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) +} diff --git a/database/database.go b/database/database.go index c1e3498..b5c5a5f 100644 --- a/database/database.go +++ b/database/database.go @@ -26,8 +26,6 @@ type Database interface { type GetBookmarksOptions struct { WithContents bool OrderLatest bool - Limit int - Offset int } func checkError(err error) { diff --git a/database/sqlite.go b/database/sqlite.go index 69a1005..ed529de 100644 --- a/database/sqlite.go +++ b/database/sqlite.go @@ -216,10 +216,6 @@ func (db *SQLiteDatabase) GetBookmarks(options GetBookmarksOptions, indices ...s query += ` ORDER BY modified DESC` } - if options.Limit > 0 { - query += fmt.Sprintf(` LIMIT %d OFFSET %d`, options.Limit, options.Offset) - } - bookmarks := []model.Bookmark{} err := db.Select(&bookmarks, query, args...) if err != nil && err != sql.ErrNoRows { diff --git a/view/css/stylesheet.css b/view/css/stylesheet.css index 045a419..39b0e53 100644 --- a/view/css/stylesheet.css +++ b/view/css/stylesheet.css @@ -1 +1 @@ -.header-link{border-right:1px solid #E5E5E5;color:#000;cursor:pointer;font-size:.9em;line-height:70px;overflow:hidden;padding:0 16px}.header-link:hover{color:#3F51B5}*{border-width:0;box-sizing:border-box;font-family:"Source Sans Pro",sans-serif;margin:0;padding:0;text-decoration:none}#app{background-color:#F5F5F5;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;height:auto;min-height:100vh}#app #header{background-color:#FFF;box-shadow:0 0 3px rgba(0,0,0,0.3);left:0;position:fixed;right:0;top:0;z-index:99;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap}#app #header #logo{border-left:1px solid #E5E5E5;cursor:default;flex-shrink:0;border-right:1px solid #E5E5E5;color:#000;cursor:pointer;font-size:.9em;overflow:hidden;padding:0 16px;line-height:70px;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;font-size:1.5em;font-weight:100;color:#3F51B5}#app #header #logo:hover{color:#3F51B5}#app #header #logo span{margin-right:8px}#app #header #logo:hover{background-color:#F5F5F5}#app #header #search-box{-webkit-box-align:center;align-items:center;border-right:1px solid #E5E5E5;display:-webkit-box;display:flex;-webkit-box-flex:1;flex:1 0;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:16px;width:100%}#app #header #search-box button,#app #header #search-box input{background-color:#FFF;border:1px solid #E5E5E5;color:#000;font-size:.9em;padding:8px}#app #header #search-box button{cursor:pointer;color:#535A60}#app #header #search-box button:hover{color:#F44336}#app #header #search-box input{border-right:0;-webkit-box-flex:1;flex:1 0;padding:8px 16px}#app #main{margin-top:70px;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap}#app #main #new-bookmark{align-self:center;max-width:600px;width:100%;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;margin:32px 8px 20px}#app #main #new-bookmark button,#app #main #new-bookmark input{background-color:#FFF;border:1px solid #E5E5E5;color:#000;font-size:.9em;padding:8px}#app #main #new-bookmark button{cursor:pointer;color:#535A60}#app #main #new-bookmark button:hover{color:#F44336}#app #main #new-bookmark input[type=text]{border-right:0;-webkit-box-flex:1;flex:1 0;padding:8px 16px}#app #main #grid{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row;padding:4px}#app #main #grid>.column{-webkit-box-flex:1;flex:1 0;padding:12px}#app #main #grid>.column>*:not(:last-child){margin-bottom:24px}#app #main #progress-bar{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column;-webkit-box-align:center;align-items:center;padding:32px}#app #main #progress-bar i{color:#6F757A;font-size:3em}#app #main #progress-bar a{color:#3F51B5 !important;font-size:.9em}#app #main #progress-bar a:hover{color:#F44336 !important}.bookmark{background-color:#FFF;border:1px solid #E5E5E5;position:relative}.bookmark .checkbox{z-index:9;right:0;opacity:0;position:absolute;outline:1px solid #E5E5E5;color:#6F757A;background-color:#FFF}.bookmark .checkbox::before{width:32px;line-height:32px;text-align:center;display:block;font-family:"Font Awesome 5 Free";font-weight:900;font-size:.9em;content:"\f00c"}.bookmark .checkbox:hover{color:#F44336 !important}.bookmark .bookmark-image{position:relative;display:block;height:250px;background-position:center;background-repeat:no-repeat;background-size:cover}.bookmark .bookmark-image .bookmark-metadata{position:absolute;top:0;left:0;width:100%;height:100%;padding:16px;-webkit-box-pack:end;justify-content:flex-end;background-color:rgba(0,0,0,0.5);border-bottom:0}.bookmark .bookmark-image .bookmark-metadata .bookmark-time,.bookmark .bookmark-image .bookmark-metadata .bookmark-url{color:white;text-shadow:1px 1px 1px rgba(0,0,0,0.5)}.bookmark .bookmark-image .bookmark-metadata .bookmark-title{color:white;text-shadow:1px 1px 1px rgba(0,0,0,0.5)}.bookmark .bookmark-image .bookmark-metadata:hover .bookmark-title{text-decoration:underline}.bookmark .bookmark-metadata{padding:16px;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap}.bookmark .bookmark-metadata .bookmark-time{color:#6F757A;font-size:.9em;margin-bottom:8px}.bookmark .bookmark-metadata .bookmark-title{color:#000;font-size:1.3em;font-weight:600}.bookmark .bookmark-metadata .bookmark-url{color:#6F757A;font-size:.9em;margin-bottom:8px;margin-bottom:0;margin-top:8px}.bookmark .bookmark-metadata:hover .bookmark-title{text-decoration:underline}.bookmark .bookmark-excerpt{padding:16px;color:#000;border-top:1px solid #E5E5E5}.bookmark .bookmark-tags{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;padding:0 12px 12px;margin-top:-4px}.bookmark .bookmark-tags a{font-size:.9em;padding:4px;color:#3F51B5 !important}.bookmark .bookmark-tags a::before{content:"#"}.bookmark .bookmark-tags a:hover{text-decoration:underline}.bookmark:hover .checkbox{opacity:1}.bookmark.checked{border:0;outline:6px solid #9E9E9E}.bookmark.checked .checkbox{opacity:1;outline:0;background-color:#9E9E9E;color:white} \ No newline at end of file +.header-link{border-right:1px solid #E5E5E5;color:#000;cursor:pointer;font-size:.9em;line-height:70px;overflow:hidden;padding:0 16px}.header-link:hover{color:#3F51B5}*{border-width:0;box-sizing:border-box;font-family:"Source Sans Pro",sans-serif;margin:0;padding:0;text-decoration:none}.spacer{-webkit-box-flex:1;flex:1 0}#app{background-color:#F5F5F5;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;height:auto;min-height:100vh}#app #header{background-color:#FFF;box-shadow:0 0 3px rgba(0,0,0,0.3);left:0;position:fixed;right:0;top:0;z-index:99;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap}#app #header #logo{border-left:1px solid #E5E5E5;cursor:default;flex-shrink:0;border-right:1px solid #E5E5E5;color:#000;cursor:pointer;font-size:.9em;overflow:hidden;padding:0 16px;line-height:70px;display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;font-size:1.5em;font-weight:100;color:#3F51B5}#app #header #logo:hover{color:#3F51B5}#app #header #logo span{margin-right:8px}#app #header #logo:hover{background-color:#F5F5F5}#app #header #search-box{-webkit-box-align:center;align-items:center;border-right:1px solid #E5E5E5;display:-webkit-box;display:flex;-webkit-box-flex:1;flex:1 0;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:16px;width:100%}#app #header #search-box .button,#app #header #search-box input{background-color:#FFF;border:1px solid #E5E5E5;color:#000;font-size:.9em;padding:8px}#app #header #search-box .button{cursor:pointer;color:#535A60}#app #header #search-box .button:hover{color:#F44336}#app #header #search-box input{border-right:0;-webkit-box-flex:1;flex:1 0;padding:8px 16px}#app #main{margin-top:70px;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap}#app #main #new-bookmark{align-self:center;max-width:600px;width:100%;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;margin:32px 8px 20px;background-color:#FFF;outline:1px solid #E5E5E5}#app #main #new-bookmark input[type=text],#app #main #new-bookmark textarea{outline:1px solid #E5E5E5;color:#000;font-size:.9em;padding:12px 16px}#app #main #new-bookmark textarea{resize:vertical;min-height:4em;max-height:10em}#app #main #new-bookmark .button-area{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:8px}#app #main #new-bookmark .button-area a{color:#535A60;text-transform:uppercase;padding:8px;background-color:#FFF}#app #main #new-bookmark .button-area a.button{font-size:.9em;cursor:pointer}#app #main #new-bookmark .button-area a.button:hover{color:#F44336}#app #main #grid{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row;padding:4px}#app #main #grid>.column{-webkit-box-flex:1;flex:1 0;padding:12px}#app #main #grid>.column>*:not(:last-child){margin-bottom:24px}#app #main #progress-bar{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column;-webkit-box-align:center;align-items:center;padding:32px}#app #main #progress-bar i{color:#6F757A;font-size:3em}#app #main #progress-bar a{color:#3F51B5 !important;font-size:.9em}#app #main #progress-bar a:hover{color:#F44336 !important}.bookmark{background-color:#FFF;border:1px solid #E5E5E5;position:relative}.bookmark .checkbox{z-index:9;right:0;opacity:0;position:absolute;outline:1px solid #E5E5E5;color:#535A60;background-color:#FFF;width:32px;line-height:32px;text-align:center;display:block;font-size:.9em}.bookmark .checkbox:hover{color:#F44336 !important}.bookmark .bookmark-metadata{padding:16px;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap}.bookmark .bookmark-metadata .bookmark-time{color:#6F757A;font-size:.9em;margin-bottom:8px}.bookmark .bookmark-metadata .bookmark-title{color:#000;font-size:1.3em;font-weight:600}.bookmark .bookmark-metadata .bookmark-url{color:#6F757A;font-size:.9em;margin-bottom:8px;margin-bottom:0;margin-top:8px;max-height:2.6em;line-height:1.3em;text-overflow:ellipsis;overflow:hidden}.bookmark .bookmark-metadata.has-image{min-height:250px;background-position:center;background-repeat:no-repeat;background-size:cover;-webkit-box-pack:end;justify-content:flex-end;position:relative}.bookmark .bookmark-metadata.has-image::before{content:"";background-color:rgba(0,0,0,0.5);position:absolute;top:0;left:0;right:0;bottom:0;z-index:0}.bookmark .bookmark-metadata.has-image .bookmark-time,.bookmark .bookmark-metadata.has-image .bookmark-url{z-index:2;color:white;text-shadow:1px 1px 1px rgba(0,0,0,0.5)}.bookmark .bookmark-metadata.has-image .bookmark-title{z-index:2;color:white;text-shadow:1px 1px 1px rgba(0,0,0,0.5)}.bookmark .bookmark-metadata:hover .bookmark-title{text-decoration:underline}.bookmark .bookmark-excerpt{padding:16px;color:#000;border-top:1px solid #E5E5E5}.bookmark .bookmark-tags{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;padding:0 12px 12px;margin-top:-4px}.bookmark .bookmark-tags a{font-size:.9em;padding:4px;color:#3F51B5 !important}.bookmark .bookmark-tags a::before{content:"#"}.bookmark .bookmark-tags a:hover{text-decoration:underline}.bookmark .bookmark-menu{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;border-top:1px solid #E5E5E5;visibility:hidden}.bookmark .bookmark-menu a{display:block;-webkit-box-flex:1;flex:1 0;color:#535A60 !important;padding:8px;font-size:.9em;text-align:center}.bookmark .bookmark-menu a:not(:last-child){border-right:1px solid #E5E5E5}.bookmark .bookmark-menu a:hover{color:#F44336 !important}.bookmark:hover .checkbox{opacity:1}.bookmark:hover .bookmark-menu{visibility:visible}.bookmark.checked{border:0;outline:6px solid #9E9E9E}.bookmark.checked .checkbox{opacity:1;outline:0;background-color:#9E9E9E;color:white} \ No newline at end of file diff --git a/view/index.html b/view/index.html index b4ce4d8..5a4eece 100644 --- a/view/index.html +++ b/view/index.html @@ -20,58 +20,61 @@ shiori
- - + +
@@ -84,22 +87,69 @@ el: '#app', data: { windowWidth: 0, - isLoading: true, - bookmarks: [] + loading: true, + bookmarks: [], + newBookmark: { + url: "", + title: "", + tags: "", + excerpt: "", + loading: false + } }, methods: { loadData: function () { - this.isLoading = true; + this.loading = true; instance.get('/api/bookmarks') .then(function (response) { - app.isLoading = false; + app.loading = false; app.bookmarks = response.data; - console.log(JSON.stringify(app.gridColumns, "", " ")); }) .catch(function (error) { + app.loading = false; 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) { var time = book.modified, readTime = "", @@ -114,13 +164,27 @@ } finalBookmarkTime = "Updated " + time; - if (readTime !== "") finalBookmarkTime += "\u00B7 " + readTime; + if (readTime !== "") finalBookmarkTime += " \u00B7 " + readTime; return finalBookmarkTime; }, bookmarkImage: function (book) { - var newURL = "background-image: url(" + book.imageURL + ")"; - return newURL; + if (book.imageURL === "") return ""; + 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: { diff --git a/view/less/stylesheet.less b/view/less/stylesheet.less index 1399324..b3cdc8e 100644 --- a/view/less/stylesheet.less +++ b/view/less/stylesheet.less @@ -8,6 +8,10 @@ text-decoration: none; } +.spacer { + flex: 1 0; +} + #app { background-color: @appBg; display: flex; @@ -50,7 +54,7 @@ flex-flow: row nowrap; padding: 16px; width: 100%; - button, + .button, input { background-color: @headerInputBg; border: 1px solid @border; @@ -58,7 +62,7 @@ font-size: 0.9em; padding: 8px; } - button { + .button { cursor: pointer; color: @linkColor; &:hover { @@ -81,28 +85,40 @@ max-width: 600px; width: 100%; display: flex; - flex-flow: row nowrap; + flex-flow: column nowrap; margin: 32px 8px 20px; - button, - input { - background-color: @headerInputBg; - border: 1px solid @border; + background-color: @headerInputBg; + outline: 1px solid @border; + input[type=text], + textarea { + outline: 1px solid @border; color: @fontColor; 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; - } - button { - cursor: pointer; - color: @linkColor; - &:hover { - color: @accent; + a { + color: @linkColor; + text-transform: uppercase; + padding: 8px; + background-color: @headerInputBg; + &.button { + font-size: 0.9em; + cursor: pointer; + &:hover { + color: @accent; + } + } } } - input[type=text] { - border-right: 0; - flex: 1 0; - padding: 8px 16px; - } } #grid { display: flex; @@ -146,55 +162,17 @@ opacity: 0; position: absolute; outline: 1px solid @border; - color: @fontLightColor; + color: @linkColor; background-color: @headerBg; - &::before { - width: 32px; - line-height: 32px; - text-align: center; - display: block; - font-family: "Font Awesome 5 Free"; - font-weight: 900; - font-size: 0.9em; - content: "\f00c"; - } + width: 32px; + line-height: 32px; + text-align: center; + display: block; + font-size: 0.9em; &:hover { 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 { padding: 16px; display: flex; @@ -213,6 +191,39 @@ .bookmark-time; margin-bottom: 0; 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 { .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 { .checkbox { opacity: 1; } + .bookmark-menu { + visibility: visible; + } } &.checked { border: 0;