diff --git a/cmd/add.go b/cmd/add.go index 8b16d88c..cc0caaab 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -5,7 +5,7 @@ import ( "github.com/RadhiFadlillah/shiori/model" "github.com/spf13/cobra" "html/template" - "os" + "strings" "time" ) @@ -22,14 +22,26 @@ var ( tags, _ := cmd.Flags().GetStringSlice("tags") offline, _ := cmd.Flags().GetBool("offline") - // Save new bookmark - bookmark, err := addBookmark(url, title, excerpt, tags, offline) - if err != nil { - cError.Println(err) - os.Exit(1) + // Create bookmark item + bookmark := model.Bookmark{ + URL: url, + Title: normalizeSpace(title), + Excerpt: normalizeSpace(excerpt), } - printBookmark(bookmark) + bookmark.Tags = make([]model.Tag, len(tags)) + for i, tag := range tags { + bookmark.Tags[i].Name = tag + } + + // Save new bookmark + result, err := addBookmark(bookmark, offline) + if err != nil { + cError.Println(err) + return + } + + printBookmark(result) }, } ) @@ -42,53 +54,42 @@ func init() { rootCmd.AddCommand(addCmd) } -func addBookmark(url, title, excerpt string, tags []string, offline bool) (book model.Bookmark, err error) { +func addBookmark(base model.Bookmark, offline bool) (book model.Bookmark, err error) { + // Prepare initial result + book = base + // Fetch data from internet - article := readability.Article{} if !offline { - article, err = readability.Parse(url, 10*time.Second) + article, err := readability.Parse(book.URL, 10*time.Second) if err != nil { cError.Println("Failed to fetch article from internet:", err) - article.URL = url - article.Meta.Title = "Untitled" + if book.Title == "" { + book.Title = "Untitled" + } + } else { + book.URL = article.URL + book.ImageURL = article.Meta.Image + book.Author = article.Meta.Author + book.MinReadTime = article.Meta.MinReadTime + book.MaxReadTime = article.Meta.MaxReadTime + book.Content = article.Content + book.HTML = template.HTML(article.RawContent) + + if book.Title == "" { + book.Title = article.Meta.Title + } + + if book.Excerpt == "" { + book.Excerpt = article.Meta.Excerpt + } } } - // Prepare bookmark - bookmark := model.Bookmark{ - URL: article.URL, - Title: article.Meta.Title, - ImageURL: article.Meta.Image, - Excerpt: article.Meta.Excerpt, - Author: article.Meta.Author, - MinReadTime: article.Meta.MinReadTime, - MaxReadTime: article.Meta.MaxReadTime, - Content: article.Content, - HTML: template.HTML(article.RawContent), - } - - bookTags := make([]model.Tag, len(tags)) - for i, tag := range tags { - bookTags[i].Name = tag - } - - bookmark.Tags = bookTags - - // Set custom value - if title != "" { - bookmark.Title = title - } - - if excerpt != "" { - bookmark.Excerpt = excerpt - } - // Save to database - bookmark.ID, err = DB.CreateBookmark(bookmark) - if err != nil { - return book, err - } - - bookmark.Modified = time.Now().UTC().Format("2006-01-02 15:04:05") - return bookmark, nil + book.ID, err = DB.CreateBookmark(book) + return book, err +} + +func normalizeSpace(str string) string { + return strings.Join(strings.Fields(str), " ") } diff --git a/cmd/import.go b/cmd/import.go new file mode 100644 index 00000000..801337ad --- /dev/null +++ b/cmd/import.go @@ -0,0 +1,113 @@ +package cmd + +import ( + "fmt" + "github.com/PuerkitoBio/goquery" + "github.com/RadhiFadlillah/shiori/model" + "github.com/spf13/cobra" + "os" + "strconv" + "strings" + "time" +) + +var ( + importCmd = &cobra.Command{ + Use: "import file", + Short: "Import bookmarks from HTML file in Firefox format.", + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + generateTag := cmd.Flags().Changed("generate-tag") + if !generateTag { + var submitGenerateTag string + fmt.Print("Add parents folder as tag? (y/n): ") + fmt.Scanln(&submitGenerateTag) + + generateTag = submitGenerateTag == "y" + } + + err := importBookmarks(args[0], generateTag) + if err != nil { + cError.Println(err) + return + } + }, + } +) + +func init() { + importCmd.Flags().BoolP("generate-tag", "t", false, "Auto generate tag from bookmark's category") + rootCmd.AddCommand(importCmd) +} + +func importBookmarks(pth string, generateTag bool) error { + // Open file + srcFile, err := os.Open(pth) + if err != nil { + return err + } + defer srcFile.Close() + + // Parse file + doc, err := goquery.NewDocumentFromReader(srcFile) + if err != nil { + return err + } + + // Fetch each bookmark categories + bookmarks := []model.Bookmark{} + doc.Find("body>dl>dt").Each(func(_ int, el *goquery.Selection) { + // Create category title + category := el.Find("h3").First().Text() + category = normalizeSpace(category) + category = strings.ToLower(category) + category = strings.Replace(category, " ", "-", -1) + + // Fetch all link in this categories + el.Find("dl>dt").Each(func(_ int, dt *goquery.Selection) { + // Get bookmark link + a := dt.Find("a").First() + title := a.Text() + url, _ := a.Attr("href") + strModified, _ := a.Attr("last_modified") + intModified, _ := strconv.ParseInt(strModified, 10, 64) + modified := time.Unix(intModified, 0) + + // Get bookmark excerpt + excerpt := "" + if nxt := dt.Next(); nxt.Is("dd") { + excerpt = nxt.Text() + } + + // Create bookmark item + bookmark := model.Bookmark{ + URL: url, + Title: normalizeSpace(title), + Excerpt: normalizeSpace(excerpt), + Modified: modified.Format("2006-01-02 15:04:05"), + Tags: []model.Tag{}, + } + + if generateTag { + bookmark.Tags = []model.Tag{ + {Name: category}, + } + } + + bookmarks = append(bookmarks, bookmark) + }) + }) + + // Save bookmarks to database + for _, book := range bookmarks { + result, err := addBookmark(book, true) + if err != nil { + cError.Printf("URL %s already exists\n\n", book.URL) + continue + } + + printBookmark(result) + } + + return nil +} diff --git a/cmd/serve.go b/cmd/serve.go index aa173113..cd31b9ef 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -218,12 +218,7 @@ func apiInsertBookmarks(w http.ResponseWriter, r *http.Request, ps httprouter.Pa 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) + book, err := addBookmark(request, false) checkError(err) // Return new saved result diff --git a/database/sqlite.go b/database/sqlite.go index c1ca39c9..fec9e0ee 100644 --- a/database/sqlite.go +++ b/database/sqlite.go @@ -9,6 +9,7 @@ import ( "sort" "strconv" "strings" + "time" ) // SQLiteDatabase is implementation of Database interface for connecting to SQLite3 database. @@ -87,6 +88,10 @@ func (db *SQLiteDatabase) CreateBookmark(bookmark model.Bookmark) (bookmarkID in return -1, fmt.Errorf("Title must not empty") } + if bookmark.Modified == "" { + bookmark.Modified = time.Now().UTC().Format("2006-01-02 15:04:05") + } + // Prepare transaction tx, err := db.Beginx() if err != nil { @@ -107,15 +112,16 @@ func (db *SQLiteDatabase) CreateBookmark(bookmark model.Bookmark) (bookmarkID in // Save article to database res := tx.MustExec(`INSERT INTO bookmark ( url, title, image_url, excerpt, author, - min_read_time, max_read_time) - VALUES(?, ?, ?, ?, ?, ?, ?)`, + min_read_time, max_read_time, modified) + VALUES(?, ?, ?, ?, ?, ?, ?, ?)`, bookmark.URL, bookmark.Title, bookmark.ImageURL, bookmark.Excerpt, bookmark.Author, bookmark.MinReadTime, - bookmark.MaxReadTime) + bookmark.MaxReadTime, + bookmark.Modified) // Get last inserted ID bookmarkID, err = res.LastInsertId() diff --git a/view/css/stylesheet.css b/view/css/stylesheet.css index 07cdca73..f11134bb 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:#F44336}*{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}#login-page{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;-webkit-box-align:center;align-items:center;height:100vh;background-color:#F5F5F5;-webkit-box-pack:center;justify-content:center}#login-page>.error-message{width:100%;margin:16px 16px 0;max-width:400px;background-color:#FFF;border:1px solid #E5E5E5;padding:16px;text-align:center}#login-page #login-box{width:100%;margin:16px;max-width:400px;background-color:#FFF;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;border:1px solid #E5E5E5}#login-page #login-box #logo-area{display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;padding:16px;border-bottom:1px solid #E5E5E5}#login-page #login-box #logo-area #logo{font-size:3em;font-weight:100;color:#F44336}#login-page #login-box #logo-area #logo span{margin-right:8px}#login-page #login-box #logo-area #tagline{font-weight:100;color:#F44336}#login-page #login-box #input-area{padding:8px;border-bottom:1px solid #E5E5E5}#login-page #login-box #input-area .input-field{display:-webkit-box;display:flex;-webkit-box-align:baseline;align-items:baseline;padding:8px}#login-page #login-box #input-area .input-field p{color:#6F757A;font-size:.9em;margin-right:16px;min-width:65px}#login-page #login-box #input-area .input-field input{color:#000;padding:8px;border:1px solid #E5E5E5;-webkit-box-flex:1;flex:1 0;font-size:.9em}#login-page #login-box #input-area .input-field a{display:block;cursor:pointer;color:#6F757A;text-align:center;font-size:.9em;-webkit-box-flex:1;flex:1 0}#login-page #login-box #input-area .input-field a i{margin-right:8px;color:#6F757A}#login-page #login-box #input-area .input-field a:hover{color:#F44336}#login-page #login-box #button-area{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:16px}#login-page #login-box #button-area a{color:#535A60;text-transform:uppercase;background-color:#FFF;-webkit-box-flex:1;flex:1 0;text-align:center}#login-page #login-box #button-area a.button{cursor:pointer}#login-page #login-box #button-area a.button:hover{color:#F44336}#main-page{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}#main-page #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}#main-page #header #n-selected{line-height:70px;font-size:1.3em;color:#6F757A;-webkit-box-flex:1;flex:1 0;border-right:1px solid #E5E5E5;padding:0 32px}#main-page #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:#F44336}#main-page #header #logo:hover{color:#F44336}#main-page #header #logo span{margin-right:8px}#main-page #header #logo:hover{background-color:#F5F5F5}#main-page #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%}#main-page #header #search-box .button,#main-page #header #search-box input{background-color:#FFF;border:1px solid #E5E5E5;color:#000;font-size:.9em;padding:8px}#main-page #header #search-box .button{cursor:pointer;color:#535A60}#main-page #header #search-box .button:hover{color:#F44336}#main-page #header #search-box input{border-right:0;-webkit-box-flex:1;flex:1 0;padding:8px 16px}#main-page #header #header-menu{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap}#main-page #header #header-menu a{line-height:70px;padding:0 16px;color:#535A60;font-size:.9em;cursor:pointer}#main-page #header #header-menu a:not(:last-child){border-right:1px solid #E5E5E5}#main-page #header #header-menu a i{margin-right:4px}#main-page #header #header-menu a:hover{color:#F44336;background-color:#F5F5F5}#main-page #main{margin-top:70px;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap}#main-page #main #input-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}#main-page #main #input-bookmark>p{color:#000;font-weight:600;text-transform:uppercase;padding:16px}#main-page #main #input-bookmark>p.error-message{color:#F44336;font-size:.9em;border-bottom:1px solid #E5E5E5;font-weight:500;text-transform:none}#main-page #main #input-bookmark input[type=text],#main-page #main #input-bookmark textarea{outline:1px solid #E5E5E5;color:#000;font-size:.9em;padding:12px 16px}#main-page #main #input-bookmark textarea{resize:vertical;min-height:4em;max-height:10em}#main-page #main #input-bookmark .button-area{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:8px}#main-page #main #input-bookmark .button-area a{color:#535A60;text-transform:uppercase;padding:8px;background-color:#FFF;font-size:.9em}#main-page #main #input-bookmark .button-area a.button{cursor:pointer}#main-page #main #input-bookmark .button-area a.button:hover{color:#F44336}#main-page #main #search-parameter{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;padding:0 8px}#main-page #main #search-parameter a{display:block;margin:8px;padding:8px;font-size:.9em;background-color:#6F757A;color:white;border-radius:16px;cursor:pointer}#main-page #main #search-parameter a:hover{background-color:#F44336;text-decoration:line-through}#main-page #main #grid{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row;padding:4px}#main-page #main #grid>.column{-webkit-box-flex:1;flex:1 0;padding:12px}#main-page #main #grid>.column>*:not(:last-child){margin-bottom:24px}#main-page #main #message-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;-webkit-box-pack:center;justify-content:center;position:absolute;top:50%;left:0;width:100%;margin-top:-60px;height:120px}#main-page #main #message-bar i{color:#6F757A;font-size:3em}#cache-page{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;-webkit-box-align:center;align-items:center;height:auto;min-height:100vh}#cache-page a{color:#F44336}#cache-page a:visited{color:#F44336}#cache-page a:hover{text-decoration:underline}#cache-page>*{width:100%;max-width:864px}#cache-page #menu{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;max-width:864px;border-bottom:1px solid #E5E5E5}#cache-page #menu a{-webkit-box-flex:1;flex:1 0;font-size:.9em;text-align:center;color:#535A60;padding:16px;cursor:pointer}#cache-page #menu a i{margin-right:4px}#cache-page #menu a:not(:last-child){border-right:1px solid #E5E5E5}#cache-page #menu a:visited{color:#535A60}#cache-page #menu a:hover{color:#F44336;text-decoration:none}#cache-page #metadata{padding:32px;border-bottom:1px solid #E5E5E5}#cache-page #metadata a{font-size:.9em;display:block}#cache-page #metadata h3{font-size:2em;margin:8px 0}#cache-page #metadata p{font-size:.9em;color:#000}#cache-page #content{padding:16px 32px 32px}#cache-page #content *{margin-top:16px;line-height:180%;overflow:auto}#cache-page #content pre,#cache-page #content code{font-family:'Ubuntu Mono','Courier New',Courier,monospace}#cache-page.dark-mode{background-color:#222;color:white}#cache-page.dark-mode #menu a{color:white}#cache-page.dark-mode #menu a:visited{color:white}#cache-page.dark-mode #menu a:hover{color:#F44336;text-decoration:none}#cache-page.dark-mode #metadata p{color:white}#dialog-overlay{position:fixed;z-index:101;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column;background-color:rgba(0,0,0,0.5);top:0;left:0;right:0;bottom:0;overflow:hidden;-webkit-box-pack:center;justify-content:center;padding:32px}#dialog-overlay #dialog{display:-webkit-box;display:flex;background-color:#FFF;align-self:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column;border:1px solid #E5E5E5;max-width:500px}#dialog-overlay #dialog #dialog-title{color:#000;font-weight:600;text-transform:uppercase;padding:16px;font-size:1em;border-bottom:1px solid #E5E5E5}#dialog-overlay #dialog #dialog-content{padding:16px}#dialog-overlay #dialog #dialog-button{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:8px;border-top:1px solid #E5E5E5}#dialog-overlay #dialog #dialog-button a{color:#535A60;text-transform:uppercase;padding:8px;background-color:#FFF}#dialog-overlay #dialog #dialog-button a.button{cursor:pointer}#dialog-overlay #dialog #dialog-button a.button:not(:last-child){margin-right:16px}#dialog-overlay #dialog #dialog-button a.button:hover{color:#F44336}.error-message{color:#F44336 !important;font-size:.9em}.error-message::before{content:"\f071";font-weight:900;margin-right:8px;font-family:"Font Awesome 5 Free"}.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;cursor:pointer;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;border-bottom:1px solid #E5E5E5}.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 16px 0;color:#000}.bookmark .bookmark-tags{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;padding:12px 12px 0;margin-bottom:-4px}.bookmark .bookmark-tags a{cursor:pointer;font-size:.9em;padding:4px;color:#F44336 !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;margin-top:16px}.bookmark .bookmark-menu a{cursor:pointer;display:block;-webkit-box-flex:1;flex:1 0;color:#535A60 !important;padding:8px;font-size:.9em;text-align:center}.bookmark .bookmark-menu a i{margin-right:4px}.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:1px solid #9E9E9E;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:#F44336}*{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}#login-page{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;-webkit-box-align:center;align-items:center;height:100vh;background-color:#F5F5F5;-webkit-box-pack:center;justify-content:center}#login-page>.error-message{width:100%;margin:16px 16px 0;max-width:400px;background-color:#FFF;border:1px solid #E5E5E5;padding:16px;text-align:center}#login-page #login-box{width:100%;margin:16px;max-width:400px;background-color:#FFF;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;border:1px solid #E5E5E5}#login-page #login-box #logo-area{display:-webkit-box;display:flex;-webkit-box-align:center;align-items:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;padding:16px;border-bottom:1px solid #E5E5E5}#login-page #login-box #logo-area #logo{font-size:3em;font-weight:100;color:#F44336}#login-page #login-box #logo-area #logo span{margin-right:8px}#login-page #login-box #logo-area #tagline{font-weight:100;color:#F44336}#login-page #login-box #input-area{padding:8px;border-bottom:1px solid #E5E5E5}#login-page #login-box #input-area .input-field{display:-webkit-box;display:flex;-webkit-box-align:baseline;align-items:baseline;padding:8px}#login-page #login-box #input-area .input-field p{color:#6F757A;font-size:.9em;margin-right:16px;min-width:65px}#login-page #login-box #input-area .input-field input{color:#000;padding:8px;border:1px solid #E5E5E5;-webkit-box-flex:1;flex:1 0;font-size:.9em}#login-page #login-box #input-area .input-field a{display:block;cursor:pointer;color:#6F757A;text-align:center;font-size:.9em;-webkit-box-flex:1;flex:1 0}#login-page #login-box #input-area .input-field a i{margin-right:8px;color:#6F757A}#login-page #login-box #input-area .input-field a:hover{color:#F44336}#login-page #login-box #button-area{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:16px}#login-page #login-box #button-area a{color:#535A60;text-transform:uppercase;background-color:#FFF;-webkit-box-flex:1;flex:1 0;text-align:center}#login-page #login-box #button-area a.button{cursor:pointer}#login-page #login-box #button-area a.button:hover{color:#F44336}#main-page{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}#main-page #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}#main-page #header #n-selected{line-height:70px;font-size:1.3em;color:#6F757A;-webkit-box-flex:1;flex:1 0;border-right:1px solid #E5E5E5;padding:0 32px}#main-page #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:#F44336}#main-page #header #logo:hover{color:#F44336}#main-page #header #logo span{margin-right:8px}#main-page #header #logo:hover{background-color:#F5F5F5}#main-page #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%}#main-page #header #search-box .button,#main-page #header #search-box input{background-color:#FFF;border:1px solid #E5E5E5;color:#000;font-size:.9em;padding:8px}#main-page #header #search-box .button{cursor:pointer;color:#535A60}#main-page #header #search-box .button:hover{color:#F44336}#main-page #header #search-box input{border-right:0;-webkit-box-flex:1;flex:1 0;padding:8px 16px}#main-page #header #header-menu{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap}#main-page #header #header-menu a{line-height:70px;padding:0 16px;color:#535A60;font-size:.9em;cursor:pointer}#main-page #header #header-menu a:not(:last-child){border-right:1px solid #E5E5E5}#main-page #header #header-menu a i{margin-right:4px}#main-page #header #header-menu a:hover{color:#F44336;background-color:#F5F5F5}#main-page #main{margin-top:70px;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap}#main-page #main #input-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}#main-page #main #input-bookmark>p{color:#000;font-weight:600;text-transform:uppercase;padding:16px}#main-page #main #input-bookmark>p.error-message{color:#F44336;font-size:.9em;border-bottom:1px solid #E5E5E5;font-weight:500;text-transform:none}#main-page #main #input-bookmark input[type=text],#main-page #main #input-bookmark textarea{outline:1px solid #E5E5E5;color:#000;font-size:.9em;padding:12px 16px}#main-page #main #input-bookmark textarea{resize:vertical;min-height:4em;max-height:10em}#main-page #main #input-bookmark .button-area{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:8px}#main-page #main #input-bookmark .button-area a{color:#535A60;text-transform:uppercase;padding:8px;background-color:#FFF;font-size:.9em}#main-page #main #input-bookmark .button-area a.button{cursor:pointer}#main-page #main #input-bookmark .button-area a.button:hover{color:#F44336}#main-page #main #search-parameter{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;padding:0 8px}#main-page #main #search-parameter a{display:block;margin:8px;padding:8px;font-size:.9em;background-color:#6F757A;color:white;border-radius:16px;cursor:pointer}#main-page #main #search-parameter a:hover{background-color:#F44336;text-decoration:line-through}#main-page #main #grid{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row;padding:4px}#main-page #main #grid>.column{-webkit-box-flex:1;flex:1 0;padding:12px}#main-page #main #grid>.column>*:not(:last-child){margin-bottom:24px}#main-page #main #message-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;-webkit-box-pack:center;justify-content:center;position:absolute;top:50%;left:0;width:100%;margin-top:-60px;height:120px}#main-page #main #message-bar i{color:#6F757A;font-size:3em}#cache-page{display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column nowrap;-webkit-box-align:center;align-items:center;height:auto;min-height:100vh}#cache-page a{color:#F44336}#cache-page a:visited{color:#F44336}#cache-page a:hover{text-decoration:underline}#cache-page>*{width:100%;max-width:864px}#cache-page #menu{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;max-width:864px;border-bottom:1px solid #E5E5E5}#cache-page #menu a{-webkit-box-flex:1;flex:1 0;font-size:.9em;text-align:center;color:#535A60;padding:16px;cursor:pointer}#cache-page #menu a i{margin-right:4px}#cache-page #menu a:not(:last-child){border-right:1px solid #E5E5E5}#cache-page #menu a:visited{color:#535A60}#cache-page #menu a:hover{color:#F44336;text-decoration:none}#cache-page #metadata{padding:32px;border-bottom:1px solid #E5E5E5}#cache-page #metadata a{font-size:.9em;display:block}#cache-page #metadata h3{font-size:2em;margin:8px 0}#cache-page #metadata p{font-size:.9em;color:#000}#cache-page #content{padding:16px 32px 32px}#cache-page #content *{margin-top:16px;line-height:180%;overflow:auto}#cache-page #content pre,#cache-page #content code{font-family:'Ubuntu Mono','Courier New',Courier,monospace}#cache-page.dark-mode{background-color:#222;color:white}#cache-page.dark-mode #menu a{color:white}#cache-page.dark-mode #menu a:visited{color:white}#cache-page.dark-mode #menu a:hover{color:#F44336;text-decoration:none}#cache-page.dark-mode #metadata p{color:white}#dialog-overlay{position:fixed;z-index:101;display:-webkit-box;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column;background-color:rgba(0,0,0,0.5);top:0;left:0;right:0;bottom:0;overflow:hidden;-webkit-box-pack:center;justify-content:center;padding:32px}#dialog-overlay #dialog{display:-webkit-box;display:flex;background-color:#FFF;align-self:center;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-flow:column;border:1px solid #E5E5E5;max-width:500px}#dialog-overlay #dialog #dialog-title{color:#000;font-weight:600;text-transform:uppercase;padding:16px;font-size:1em;border-bottom:1px solid #E5E5E5}#dialog-overlay #dialog #dialog-content{padding:16px}#dialog-overlay #dialog #dialog-button{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row nowrap;padding:8px;border-top:1px solid #E5E5E5}#dialog-overlay #dialog #dialog-button a{color:#535A60;text-transform:uppercase;padding:8px;background-color:#FFF}#dialog-overlay #dialog #dialog-button a.button{cursor:pointer}#dialog-overlay #dialog #dialog-button a.button:not(:last-child){margin-right:16px}#dialog-overlay #dialog #dialog-button a.button:hover{color:#F44336}.error-message{color:#F44336 !important;font-size:.9em}.error-message::before{content:"\f071";font-weight:900;margin-right:8px;font-family:"Font Awesome 5 Free"}.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;cursor:pointer;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;border-bottom:1px solid #E5E5E5}.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 16px 0;color:#000}.bookmark .bookmark-tags{display:-webkit-box;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;flex-flow:row wrap;padding:12px 12px 0;margin-bottom:-4px}.bookmark .bookmark-tags a{cursor:pointer;font-size:.9em;padding:4px;color:#F44336 !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;margin-top:16px}.bookmark .bookmark-menu:nth-child(3){border-top:0;margin-top:0}.bookmark .bookmark-menu a{cursor:pointer;display:block;-webkit-box-flex:1;flex:1 0;color:#535A60 !important;padding:8px;font-size:.9em;text-align:center}.bookmark .bookmark-menu a i{margin-right:4px}.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:1px solid #9E9E9E;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/less/stylesheet.less b/view/less/stylesheet.less index 912bd195..1d6af3c8 100644 --- a/view/less/stylesheet.less +++ b/view/less/stylesheet.less @@ -582,6 +582,10 @@ border-top: 1px solid @border; visibility: hidden; margin-top: 16px; + &:nth-child(3) { + border-top: 0; + margin-top: 0; + } a { cursor: pointer; display: block;