diff options
author | Frédéric Guillot <fred@miniflux.net> | 2017-11-21 19:37:47 -0800 |
---|---|---|
committer | Frédéric Guillot <fred@miniflux.net> | 2017-11-21 19:37:47 -0800 |
commit | 6690f6a70eb319c1156f8a1bb7465e048567580a (patch) | |
tree | 33c9c481e09dd6e863e86da90144ba0d67ea98f6 | |
parent | 1bc43ec2bc2282d9e18ca622444d45f09c1b6378 (diff) |
Add bookmarklet
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | config/config.go | 4 | ||||
-rw-r--r-- | locale/translations.go | 11 | ||||
-rw-r--r-- | locale/translations/fr_FR.json | 7 | ||||
-rw-r--r-- | server/core/handler.go | 8 | ||||
-rw-r--r-- | server/core/html_response.go | 2 | ||||
-rw-r--r-- | server/core/response.go | 4 | ||||
-rw-r--r-- | server/routes.go | 8 | ||||
-rw-r--r-- | server/server.go | 10 | ||||
-rw-r--r-- | server/static/bin.go | 2 | ||||
-rw-r--r-- | server/static/css.go | 6 | ||||
-rw-r--r-- | server/static/css/common.css | 15 | ||||
-rw-r--r-- | server/static/js.go | 2 | ||||
-rw-r--r-- | server/template/common.go | 2 | ||||
-rw-r--r-- | server/template/html/about.html | 3 | ||||
-rw-r--r-- | server/template/html/create_user.html | 3 | ||||
-rw-r--r-- | server/template/html/edit_user.html | 3 | ||||
-rw-r--r-- | server/template/html/integrations.html | 35 | ||||
-rw-r--r-- | server/template/html/sessions.html | 3 | ||||
-rw-r--r-- | server/template/html/settings.html | 3 | ||||
-rw-r--r-- | server/template/html/users.html | 3 | ||||
-rw-r--r-- | server/template/template.go | 44 | ||||
-rw-r--r-- | server/template/views.go | 69 | ||||
-rw-r--r-- | server/ui/controller/integrations.go | 20 | ||||
-rw-r--r-- | server/ui/controller/subscription.go | 21 | ||||
-rw-r--r-- | sql/sql.go | 2 |
26 files changed, 244 insertions, 48 deletions
@@ -26,7 +26,7 @@ TODO - [ ] Custom entries sorting - [ ] Webpage scraper (Readability) -- [ ] Bookmarklet +- [X] Bookmarklet - [ ] External integrations (Pinboard, Wallabag...) - [ ] Gzip compression - [ ] Integration tests diff --git a/config/config.go b/config/config.go index 42fb209..fb462f8 100644 --- a/config/config.go +++ b/config/config.go @@ -9,6 +9,10 @@ import ( "strconv" ) +const ( + DefaultBaseURL = "http://localhost" +) + type Config struct { } diff --git a/locale/translations.go b/locale/translations.go index f4532a2..ed17220 100644 --- a/locale/translations.go +++ b/locale/translations.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// 2017-11-21 15:41:59.495654213 -0800 PST m=+0.041889871 +// 2017-11-21 19:31:59.645632989 -0800 PST m=+0.024631837 package locale @@ -139,12 +139,17 @@ var Translations = map[string]string{ "Work in progress...": "Travail en cours...", "This user already exists.": "Cet utilisateur existe déjà.", "This category already exists.": "Cette catégorie existe déjà.", - "Unable to update this category.": "Impossible de mettre à jour cette catégorie." + "Unable to update this category.": "Impossible de mettre à jour cette catégorie.", + "Integrations": "Intégrations", + "Bookmarklet": "Bookmarklet", + "Drag and drop this link to your bookmarks.": "Glisser-déposer ce lien dans vos favoris.", + "This special link allows you to subscribe to a website directly by using a bookmark in your web browser.": "Ce lien spécial vous permet de vous abonner à un site web directement en utilisant un marque page dans votre navigateur web.", + "Add to Miniflux": "Ajouter à Miniflux" } `, } var TranslationsChecksums = map[string]string{ "en_US": "6fe95384260941e8a5a3c695a655a932e0a8a6a572c1e45cb2b1ae8baa01b897", - "fr_FR": "5c8c2c5e35a17a7dd3c30596b73342f70950a3bbce00786d43ccba01b96ea672", + "fr_FR": "f1ddbcfb8ffd837a2df69e8506d023e4254ead2f0b94e518ab595df97d32c87a", } diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index 9cbe220..a0206ba 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -123,5 +123,10 @@ "Work in progress...": "Travail en cours...", "This user already exists.": "Cet utilisateur existe déjà.", "This category already exists.": "Cette catégorie existe déjà.", - "Unable to update this category.": "Impossible de mettre à jour cette catégorie." + "Unable to update this category.": "Impossible de mettre à jour cette catégorie.", + "Integrations": "Intégrations", + "Bookmarklet": "Bookmarklet", + "Drag and drop this link to your bookmarks.": "Glisser-déposer ce lien dans vos favoris.", + "This special link allows you to subscribe to a website directly by using a bookmark in your web browser.": "Ce lien spécial vous permet de vous abonner à un site web directement en utilisant un marque page dans votre navigateur web.", + "Add to Miniflux": "Ajouter à Miniflux" } diff --git a/server/core/handler.go b/server/core/handler.go index 8541301..df7e0be 100644 --- a/server/core/handler.go +++ b/server/core/handler.go @@ -18,16 +18,19 @@ import ( "github.com/gorilla/mux" ) +// HandlerFunc is an application HTTP handler. type HandlerFunc func(ctx *Context, request *Request, response *Response) +// Handler manages HTTP handlers and middlewares. type Handler struct { store *storage.Storage translator *locale.Translator - template *template.TemplateEngine + template *template.Engine router *mux.Router middleware *middleware.MiddlewareChain } +// Use is a wrapper around an HTTP handler. func (h *Handler) Use(f HandlerFunc) http.Handler { return h.middleware.WrapFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer helper.ExecutionTime(time.Now(), r.URL.Path) @@ -47,7 +50,8 @@ func (h *Handler) Use(f HandlerFunc) http.Handler { })) } -func NewHandler(store *storage.Storage, router *mux.Router, template *template.TemplateEngine, translator *locale.Translator, middleware *middleware.MiddlewareChain) *Handler { +// NewHandler returns a new Handler. +func NewHandler(store *storage.Storage, router *mux.Router, template *template.Engine, translator *locale.Translator, middleware *middleware.MiddlewareChain) *Handler { return &Handler{ store: store, translator: translator, diff --git a/server/core/html_response.go b/server/core/html_response.go index f10fe72..52b5145 100644 --- a/server/core/html_response.go +++ b/server/core/html_response.go @@ -15,7 +15,7 @@ import ( type HTMLResponse struct { writer http.ResponseWriter request *http.Request - template *template.TemplateEngine + template *template.Engine } // Render execute a template and send to the client the generated HTML. diff --git a/server/core/response.go b/server/core/response.go index 48403dd..3acc453 100644 --- a/server/core/response.go +++ b/server/core/response.go @@ -15,7 +15,7 @@ import ( type Response struct { writer http.ResponseWriter request *http.Request - template *template.TemplateEngine + template *template.Engine } // SetCookie send a cookie to the client. @@ -67,6 +67,6 @@ func (r *Response) commonHeaders() { } // NewResponse returns a new Response. -func NewResponse(w http.ResponseWriter, r *http.Request, template *template.TemplateEngine) *Response { +func NewResponse(w http.ResponseWriter, r *http.Request, template *template.Engine) *Response { return &Response{writer: w, request: r, template: template} } diff --git a/server/routes.go b/server/routes.go index 36471b0..97a8b2f 100644 --- a/server/routes.go +++ b/server/routes.go @@ -7,6 +7,7 @@ package server import ( "net/http" + "github.com/miniflux/miniflux2/config" "github.com/miniflux/miniflux2/locale" "github.com/miniflux/miniflux2/reader/feed" "github.com/miniflux/miniflux2/reader/opml" @@ -20,10 +21,10 @@ import ( "github.com/gorilla/mux" ) -func getRoutes(store *storage.Storage, feedHandler *feed.Handler) *mux.Router { +func getRoutes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler) *mux.Router { router := mux.NewRouter() translator := locale.Load() - templateEngine := template.NewTemplateEngine(router, translator) + templateEngine := template.NewEngine(cfg, router, translator) apiController := api_controller.NewController(store, feedHandler) uiController := ui_controller.NewController(store, feedHandler, opml.NewHandler(store)) @@ -110,6 +111,9 @@ func getRoutes(store *storage.Storage, feedHandler *feed.Handler) *mux.Router { router.Handle("/settings", uiHandler.Use(uiController.ShowSettings)).Name("settings").Methods("GET") router.Handle("/settings", uiHandler.Use(uiController.UpdateSettings)).Name("updateSettings").Methods("POST") + router.Handle("/bookmarklet", uiHandler.Use(uiController.Bookmarklet)).Name("bookmarklet").Methods("GET") + router.Handle("/integrations", uiHandler.Use(uiController.ShowIntegrations)).Name("integrations").Methods("GET") + router.Handle("/sessions", uiHandler.Use(uiController.ShowSessions)).Name("sessions").Methods("GET") router.Handle("/sessions/{sessionID}/remove", uiHandler.Use(uiController.RemoveSession)).Name("removeSession").Methods("POST") diff --git a/server/server.go b/server/server.go index ec32329..9ab0ab8 100644 --- a/server/server.go +++ b/server/server.go @@ -5,21 +5,23 @@ package server import ( - "github.com/miniflux/miniflux2/config" - "github.com/miniflux/miniflux2/reader/feed" - "github.com/miniflux/miniflux2/storage" "log" "net/http" "time" + + "github.com/miniflux/miniflux2/config" + "github.com/miniflux/miniflux2/reader/feed" + "github.com/miniflux/miniflux2/storage" ) +// NewServer returns a new HTTP server. func NewServer(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler) *http.Server { server := &http.Server{ ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 60 * time.Second, Addr: cfg.Get("LISTEN_ADDR", "127.0.0.1:8080"), - Handler: getRoutes(store, feedHandler), + Handler: getRoutes(cfg, store, feedHandler), } go func() { diff --git a/server/static/bin.go b/server/static/bin.go index ea9db5a..ae5a4b2 100644 --- a/server/static/bin.go +++ b/server/static/bin.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// 2017-11-21 15:41:59.461181295 -0800 PST m=+0.007416953 +// 2017-11-21 19:31:59.626742594 -0800 PST m=+0.005741442 package static diff --git a/server/static/css.go b/server/static/css.go index 5243f89..11e827e 100644 --- a/server/static/css.go +++ b/server/static/css.go @@ -1,14 +1,14 @@ // Code generated by go generate; DO NOT EDIT. -// 2017-11-21 15:41:59.464123652 -0800 PST m=+0.010359310 +// 2017-11-21 19:31:59.629675274 -0800 PST m=+0.008674122 package static var Stylesheets = map[string]string{ "black": `body{background:#222;color:#efefef}h1,h2,h3{color:#aaa}a{color:#aaa}a:focus,a:hover{color:#ddd}.header li{border-color:#333}.header a{color:#ddd;font-weight:400}.header .active a{font-weight:400;color:#9b9494}.header a:focus,.header a:hover{color:rgba(82,168,236,.85)}.page-header h1{border-color:#333}.logo a:hover span{color:#555}table,th,td{border:1px solid #555}th{background:#333;color:#aaa;font-weight:400}tr:hover{background-color:#333;color:#aaa}input[type=url],input[type=password],input[type=text]{border:1px solid #555;background:#333;color:#ccc}input[type=url]:focus,input[type=password]:focus,input[type=text]:focus{color:#efefef;border-color:rgba(82,168,236,.8);box-shadow:0 0 8px rgba(82,168,236,.6)}.button-primary{border-color:#444;background:#333;color:#efefef}.button-primary:hover,.button-primary:focus{border-color:#888;background:#555}.alert,.alert-success,.alert-error,.alert-info,.alert-normal{color:#efefef;background-color:#333;border-color:#444}.panel{background:#333;border-color:#555}.unread-counter{color:#bbb}.category{color:#efefef;background-color:#333;border-color:#444}.category a{color:#999}.category a:hover,.category a:focus{color:#aaa}.pagination a{color:#aaa}.pagination-bottom{border-color:#333}.item{border-color:#666;padding:4px}.item.current-item{border-width:2px;border-color:rgba(82,168,236,.8);box-shadow:0 0 8px rgba(82,168,236,.6)}.item-title a{font-weight:400}.item-status-read .item-title a{color:#666}.item-status-read .item-title a:focus,.item-status-read .item-title a:hover{color:rgba(82,168,236,.6)}.item-meta a:hover,.item-meta a:focus{color:#aaa}.item-meta li:after{color:#ddd}.entry header{border-color:#333}.entry header h1 a{color:#bbb}.entry-content,.entry-content p,ul{color:#999}.entry-content pre,.entry-content code{color:#fff;background:#555;border-color:#888}.entry-enclosure{border-color:#333}`, - "common": `*{margin:0;padding:0;box-sizing:border-box}body{font-family:helvetica neue,Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility}.main{padding-left:3px;padding-right:3px}a{color:#36c}a:focus{outline:0;color:red;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}.header{margin-top:10px;margin-bottom:20px}.header nav ul{display:none}.header li{cursor:pointer;padding-left:10px;line-height:2.1em;font-size:1.2em;border-bottom:1px dotted #ddd}.header li:hover a{color:#888}.header a{font-size:.9em;color:#444;text-decoration:none;border:0}.header .active a{font-weight:600}.header a:hover,.header a:focus{color:#888}.page-header{margin-bottom:25px}.page-header h1{font-weight:500;border-bottom:1px dotted #ddd}.page-header ul{margin-left:25px;font-size:.9em}.page-header li{list-style-type:circle;line-height:1.4em}.logo{cursor:pointer;text-align:center}.logo a{color:#000;letter-spacing:1px}.logo a:hover{color:#396}.logo a span{color:#396}.logo a:hover span{color:#000}@media(min-width:600px){body{margin:auto;max-width:750px}.logo{text-align:left;float:left;margin-right:15px}.header nav ul{display:block}.header li{display:inline;padding:0;padding-right:15px;line-height:normal;font-size:1em;border:0}.page-header ul{margin-left:0}.page-header li{display:inline;padding-right:15px}}table{width:100%;border-collapse:collapse}table,th,td{border:1px solid #ddd}th,td{padding:5px;text-align:left}td{vertical-align:top}th{background:#fcfcfc}tr:hover{background-color:#f9f9f9}.column-40{width:40%}.column-25{width:25%}.column-20{width:20%}label{cursor:pointer;display:block}.radio-group{line-height:1.9em}div.radio-group label{display:inline-block}select{margin-bottom:15px}input[type=url],input[type=password],input[type=text]{border:1px solid #ccc;padding:3px;line-height:20px;width:250px;font-size:99%;margin-bottom:10px;margin-top:5px;-webkit-appearance:none}input[type=url]:focus,input[type=password]:focus,input[type=text]:focus{color:#000;border-color:rgba(82,168,236,.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,.6)}::-moz-placeholder,::-ms-input-placeholder,::-webkit-input-placeholder{color:#ddd;padding-top:2px}.form-help{font-size:.9em;color:brown;margin-bottom:15px}a.button{text-decoration:none}.button{display:inline-block;-webkit-appearance:none;-moz-appearance:none;font-size:1.1em;cursor:pointer;padding:3px 10px;border:1px solid;border-radius:unset}.button-primary{border-color:#3079ed;background:#4d90fe;color:#fff}.button-primary:hover,.button-primary:focus{border-color:#2f5bb7;background:#357ae8}.button-danger{border-color:#b0281a;background:#d14836;color:#fff}.button-danger:hover,.button-danger:focus{color:#fff;background:#c53727}.button:disabled{color:#ccc;background:#f7f7f7;border-color:#ccc}.buttons{margin-top:10px;margin-bottom:20px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;color:#c09853;background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px;overflow:auto}.alert h3{margin-top:0;margin-bottom:15px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-error a{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.panel{color:#333;background-color:#f0f0f0;border:1px solid #ddd;border-radius:5px;padding:10px;margin-bottom:15px}.panel h3{font-weight:500;margin-top:0;margin-bottom:20px}.panel ul{margin-left:30px}.login-form{margin:auto;margin-top:50px;width:350px}.unread-counter{font-size:.8em;font-weight:300;color:#666}.category{font-size:.75em;background-color:#fffcd7;border:1px solid #d5d458;border-radius:5px;margin-left:.25em;padding:1px .4em;white-space:nowrap}.category a{color:#555;text-decoration:none}.category a:hover,.category a:focus{color:#000}.pagination{font-size:1.1em;display:flex;align-items:center;padding-top:8px}.pagination-bottom{border-top:1px dotted #ddd;margin-bottom:15px;margin-top:50px}.pagination>div{flex:1}.pagination-next{text-align:right}.pagination-prev:before{content:"« "}.pagination-next:after{content:" »"}.pagination a{color:#333}.pagination a:hover,.pagination a:focus{text-decoration:none}.item{border:1px dotted #ddd;margin-bottom:20px;padding:5px;overflow:hidden}.item.current-item{border:3px solid #bce;padding:3px}.item-title a{text-decoration:none;font-weight:600}.item-status-read .item-title a{color:#777}.item-meta{color:#777;font-size:.8em}.item-meta a{color:#777;text-decoration:none}.item-meta a:hover,.item-meta a:focus{color:#333}.item-meta ul{margin-top:5px}.item-meta li{display:inline}.item-meta li:after{content:"|";color:#aaa}.item-meta li:last-child:after{content:""}.hide-read-items .item-status-read{display:none}.entry header{padding-bottom:5px;border-bottom:1px dotted #ddd}.entry header h1{font-size:2em;line-height:1.25em;margin:30px 0}.entry header h1 a{text-decoration:none;color:#333}.entry header h1 a:hover,.entry header h1 a:focus{color:#666}.entry-meta{font-size:.95em;margin:0 0 20px;color:#666}.entry-website img{vertical-align:top}.entry-website a{color:#666;vertical-align:top;text-decoration:none}.entry-website a:hover,.entry-website a:focus{text-decoration:underline}.entry-date{font-size:.65em;font-style:italic;color:#555}.entry-content{padding-top:15px;font-size:1.1em;font-weight:300;color:#444}.entry-content h1,h2,h3,h4,h5,h6{margin-top:15px}.entry-content iframe,.entry-content video,.entry-content img{max-width:100%}.entry-content figure img{border:1px solid #000}.entry-content figcaption{font-size:.75em;text-transform:uppercase;color:#777}.entry-content p{margin-top:15px;margin-bottom:15px;text-align:justify}.entry-content a:visited{color:purple}.entry-content dt{font-weight:500;margin-top:15px;color:#555}.entry-content dd{margin-left:15px;margin-top:5px;padding-left:20px;border-left:3px solid #ddd;color:#777;font-weight:300;line-height:1.4em}.entry-content blockquote{border-left:4px solid #ddd;padding-left:25px;margin-left:20px;margin-top:20px;margin-bottom:20px;color:#888;line-height:1.4em;font-family:Georgia,serif}.entry-content blockquote+p{color:#555;font-style:italic;font-weight:200}.entry-content q{color:purple;font-family:Georgia,serif;font-style:italic}.entry-content q:before{content:"“"}.entry-content q:after{content:"”"}.entry-content pre{padding:5px;background:#f0f0f0;border:1px solid #ddd;overflow:scroll}.entry-content ul,.entry-content ol{margin-left:30px}.entry-content ul{list-style-type:square}.entry-enclosures h3{font-weight:500}.entry-enclosure{border:1px dotted #ddd;padding:5px;margin-top:10px;max-width:100%}.entry-enclosure-download{font-size:.85em}.enclosure-video video,.enclosure-image img{max-width:100%}.confirm{font-weight:500;color:#ed2d04}.confirm a{color:#ed2d04}.loading{font-style:italic}`, + "common": `*{margin:0;padding:0;box-sizing:border-box}body{font-family:helvetica neue,Helvetica,Arial,sans-serif;text-rendering:optimizeLegibility}.main{padding-left:3px;padding-right:3px}a{color:#36c}a:focus{outline:0;color:red;text-decoration:none;border:1px dotted #aaa}a:hover{color:#333;text-decoration:none}.header{margin-top:10px;margin-bottom:20px}.header nav ul{display:none}.header li{cursor:pointer;padding-left:10px;line-height:2.1em;font-size:1.2em;border-bottom:1px dotted #ddd}.header li:hover a{color:#888}.header a{font-size:.9em;color:#444;text-decoration:none;border:0}.header .active a{font-weight:600}.header a:hover,.header a:focus{color:#888}.page-header{margin-bottom:25px}.page-header h1{font-weight:500;border-bottom:1px dotted #ddd}.page-header ul{margin-left:25px;font-size:.9em}.page-header li{list-style-type:circle;line-height:1.4em}.logo{cursor:pointer;text-align:center}.logo a{color:#000;letter-spacing:1px}.logo a:hover{color:#396}.logo a span{color:#396}.logo a:hover span{color:#000}@media(min-width:600px){body{margin:auto;max-width:750px}.logo{text-align:left;float:left;margin-right:15px}.header nav ul{display:block}.header li{display:inline;padding:0;padding-right:15px;line-height:normal;font-size:1em;border:0}.page-header ul{margin-left:0}.page-header li{display:inline;padding-right:15px}}table{width:100%;border-collapse:collapse}table,th,td{border:1px solid #ddd}th,td{padding:5px;text-align:left}td{vertical-align:top}th{background:#fcfcfc}tr:hover{background-color:#f9f9f9}.column-40{width:40%}.column-25{width:25%}.column-20{width:20%}label{cursor:pointer;display:block}.radio-group{line-height:1.9em}div.radio-group label{display:inline-block}select{margin-bottom:15px}input[type=url],input[type=password],input[type=text]{border:1px solid #ccc;padding:3px;line-height:20px;width:250px;font-size:99%;margin-bottom:10px;margin-top:5px;-webkit-appearance:none}input[type=url]:focus,input[type=password]:focus,input[type=text]:focus{color:#000;border-color:rgba(82,168,236,.8);outline:0;box-shadow:0 0 8px rgba(82,168,236,.6)}::-moz-placeholder,::-ms-input-placeholder,::-webkit-input-placeholder{color:#ddd;padding-top:2px}.form-help{font-size:.9em;color:brown;margin-bottom:15px}a.button{text-decoration:none}.button{display:inline-block;-webkit-appearance:none;-moz-appearance:none;font-size:1.1em;cursor:pointer;padding:3px 10px;border:1px solid;border-radius:unset}.button-primary{border-color:#3079ed;background:#4d90fe;color:#fff}.button-primary:hover,.button-primary:focus{border-color:#2f5bb7;background:#357ae8}.button-danger{border-color:#b0281a;background:#d14836;color:#fff}.button-danger:hover,.button-danger:focus{color:#fff;background:#c53727}.button:disabled{color:#ccc;background:#f7f7f7;border-color:#ccc}.buttons{margin-top:10px;margin-bottom:20px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;color:#c09853;background-color:#fcf8e3;border:1px solid #fbeed5;border-radius:4px;overflow:auto}.alert h3{margin-top:0;margin-bottom:15px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-error a{color:#b94a48}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.panel{color:#333;background-color:#f0f0f0;border:1px solid #ddd;border-radius:5px;padding:10px;margin-bottom:15px}.panel h3{font-weight:500;margin-top:0;margin-bottom:20px}.panel ul{margin-left:30px}.login-form{margin:auto;margin-top:50px;width:350px}.unread-counter{font-size:.8em;font-weight:300;color:#666}.category{font-size:.75em;background-color:#fffcd7;border:1px solid #d5d458;border-radius:5px;margin-left:.25em;padding:1px .4em;white-space:nowrap}.category a{color:#555;text-decoration:none}.category a:hover,.category a:focus{color:#000}.pagination{font-size:1.1em;display:flex;align-items:center;padding-top:8px}.pagination-bottom{border-top:1px dotted #ddd;margin-bottom:15px;margin-top:50px}.pagination>div{flex:1}.pagination-next{text-align:right}.pagination-prev:before{content:"« "}.pagination-next:after{content:" »"}.pagination a{color:#333}.pagination a:hover,.pagination a:focus{text-decoration:none}.item{border:1px dotted #ddd;margin-bottom:20px;padding:5px;overflow:hidden}.item.current-item{border:3px solid #bce;padding:3px}.item-title a{text-decoration:none;font-weight:600}.item-status-read .item-title a{color:#777}.item-meta{color:#777;font-size:.8em}.item-meta a{color:#777;text-decoration:none}.item-meta a:hover,.item-meta a:focus{color:#333}.item-meta ul{margin-top:5px}.item-meta li{display:inline}.item-meta li:after{content:"|";color:#aaa}.item-meta li:last-child:after{content:""}.hide-read-items .item-status-read{display:none}.entry header{padding-bottom:5px;border-bottom:1px dotted #ddd}.entry header h1{font-size:2em;line-height:1.25em;margin:30px 0}.entry header h1 a{text-decoration:none;color:#333}.entry header h1 a:hover,.entry header h1 a:focus{color:#666}.entry-meta{font-size:.95em;margin:0 0 20px;color:#666}.entry-website img{vertical-align:top}.entry-website a{color:#666;vertical-align:top;text-decoration:none}.entry-website a:hover,.entry-website a:focus{text-decoration:underline}.entry-date{font-size:.65em;font-style:italic;color:#555}.entry-content{padding-top:15px;font-size:1.1em;font-weight:300;color:#444}.entry-content h1,h2,h3,h4,h5,h6{margin-top:15px}.entry-content iframe,.entry-content video,.entry-content img{max-width:100%}.entry-content figure img{border:1px solid #000}.entry-content figcaption{font-size:.75em;text-transform:uppercase;color:#777}.entry-content p{margin-top:15px;margin-bottom:15px;text-align:justify}.entry-content a:visited{color:purple}.entry-content dt{font-weight:500;margin-top:15px;color:#555}.entry-content dd{margin-left:15px;margin-top:5px;padding-left:20px;border-left:3px solid #ddd;color:#777;font-weight:300;line-height:1.4em}.entry-content blockquote{border-left:4px solid #ddd;padding-left:25px;margin-left:20px;margin-top:20px;margin-bottom:20px;color:#888;line-height:1.4em;font-family:Georgia,serif}.entry-content blockquote+p{color:#555;font-style:italic;font-weight:200}.entry-content q{color:purple;font-family:Georgia,serif;font-style:italic}.entry-content q:before{content:"“"}.entry-content q:after{content:"”"}.entry-content pre{padding:5px;background:#f0f0f0;border:1px solid #ddd;overflow:scroll}.entry-content ul,.entry-content ol{margin-left:30px}.entry-content ul{list-style-type:square}.entry-enclosures h3{font-weight:500}.entry-enclosure{border:1px dotted #ddd;padding:5px;margin-top:10px;max-width:100%}.entry-enclosure-download{font-size:.85em}.enclosure-video video,.enclosure-image img{max-width:100%}.confirm{font-weight:500;color:#ed2d04}.confirm a{color:#ed2d04}.loading{font-style:italic}.bookmarklet{border:1px dashed #ccc;border-radius:5px;padding:15px;margin:15px;text-align:center}.bookmarklet a{font-weight:600;text-decoration:none;font-size:1.2em}`, } var StylesheetsChecksums = map[string]string{ "black": "38e7fee92187a036ce37f3c15fde2deff59a55c5ab693c7b8578af79d6a117d2", - "common": "66deebc05acbfd97a2c0b04ebe0bd0205c1e99c92599df1c311b4085c39171ba", + "common": "fc802b0b281d26df08fe196f6d1d10024eaeccef42710e91e03201d08ae1b19f", } diff --git a/server/static/css/common.css b/server/static/css/common.css index 0c0fe22..3354f55 100644 --- a/server/static/css/common.css +++ b/server/static/css/common.css @@ -659,3 +659,18 @@ a.button { .loading { font-style: italic; } + +/* Bookmarlet */ +.bookmarklet { + border: 1px dashed #ccc; + border-radius: 5px; + padding: 15px; + margin: 15px; + text-align: center; +} + +.bookmarklet a { + font-weight: 600; + text-decoration: none; + font-size: 1.2em; +} diff --git a/server/static/js.go b/server/static/js.go index 23df072..07f2de0 100644 --- a/server/static/js.go +++ b/server/static/js.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// 2017-11-21 15:41:59.4687788 -0800 PST m=+0.015014458 +// 2017-11-21 19:31:59.631783539 -0800 PST m=+0.010782387 package static diff --git a/server/template/common.go b/server/template/common.go index b108f0e..2dc24c6 100644 --- a/server/template/common.go +++ b/server/template/common.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// 2017-11-21 15:41:59.491806442 -0800 PST m=+0.038042100 +// 2017-11-21 19:31:59.643994492 -0800 PST m=+0.022993340 package template diff --git a/server/template/html/about.html b/server/template/html/about.html index 3596327..24c0f2c 100644 --- a/server/template/html/about.html +++ b/server/template/html/about.html @@ -8,6 +8,9 @@ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> {{ if .user.IsAdmin }} diff --git a/server/template/html/create_user.html b/server/template/html/create_user.html index 36af356..225dfaa 100644 --- a/server/template/html/create_user.html +++ b/server/template/html/create_user.html @@ -8,6 +8,9 @@ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> <li> diff --git a/server/template/html/edit_user.html b/server/template/html/edit_user.html index 8f63307..dbbd852 100644 --- a/server/template/html/edit_user.html +++ b/server/template/html/edit_user.html @@ -8,6 +8,9 @@ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> <li> diff --git a/server/template/html/integrations.html b/server/template/html/integrations.html new file mode 100644 index 0000000..75ffbc9 --- /dev/null +++ b/server/template/html/integrations.html @@ -0,0 +1,35 @@ +{{ define "title"}}{{ t "Integrations" }}{{ end }} + +{{ define "content"}} +<section class="page-header"> + <h1>{{ t "Integrations" }}</h1> + <ul> + <li> + <a href="{{ route "settings" }}">{{ t "Settings" }}</a> + </li> + <li> + <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> + </li> + {{ if .user.IsAdmin }} + <li> + <a href="{{ route "users" }}">{{ t "Users" }}</a> + </li> + {{ end }} + <li> + <a href="{{ route "about" }}">{{ t "About" }}</a> + </li> + </ul> +</section> + +<div class="panel"> + <h3>{{ t "Bookmarklet" }}</h3> + <p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p> + + <div class="bookmarklet"> + <a href="javascript:location.href='{{ baseURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a> + </div> + + <p>{{ t "Drag and drop this link to your bookmarks." }}</p> +</div> + +{{ end }} diff --git a/server/template/html/sessions.html b/server/template/html/sessions.html index 2e01d17..6c76867 100644 --- a/server/template/html/sessions.html +++ b/server/template/html/sessions.html @@ -8,6 +8,9 @@ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "users" }}">{{ t "Users" }}</a> </li> <li> diff --git a/server/template/html/settings.html b/server/template/html/settings.html index f916708..dcad092 100644 --- a/server/template/html/settings.html +++ b/server/template/html/settings.html @@ -5,6 +5,9 @@ <h1>{{ t "Settings" }}</h1> <ul> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> {{ if .user.IsAdmin }} diff --git a/server/template/html/users.html b/server/template/html/users.html index be67d4b..40ca5e9 100644 --- a/server/template/html/users.html +++ b/server/template/html/users.html @@ -8,6 +8,9 @@ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> <li> diff --git a/server/template/template.go b/server/template/template.go index 7e2bf21..dc5283d 100644 --- a/server/template/template.go +++ b/server/template/template.go @@ -1,4 +1,4 @@ -// Copyright 2017 Frédéric Guillot. All rights reserved. +// Copyright 2017 Frédéric Guilloe. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. @@ -13,6 +13,7 @@ import ( "strings" "time" + "github.com/miniflux/miniflux2/config" "github.com/miniflux/miniflux2/errors" "github.com/miniflux/miniflux2/locale" "github.com/miniflux/miniflux2/server/route" @@ -22,23 +23,28 @@ import ( "github.com/gorilla/mux" ) -type TemplateEngine struct { +// Engine handles the templating system. +type Engine struct { templates map[string]*template.Template router *mux.Router translator *locale.Translator currentLocale *locale.Language + cfg *config.Config } -func (t *TemplateEngine) ParseAll() { +func (e *Engine) parseAll() { funcMap := template.FuncMap{ + "baseURL": func() string { + return e.cfg.Get("BASE_URL", config.DefaultBaseURL) + }, "route": func(name string, args ...interface{}) string { - return route.GetRoute(t.router, name, args...) + return route.GetRoute(e.router, name, args...) }, "noescape": func(str string) template.HTML { return template.HTML(str) }, "proxyFilter": func(data string) string { - return filter.ImageProxyFilter(t.router, data) + return filter.ImageProxyFilter(e.router, data) }, "domain": func(websiteURL string) string { parsedURL, err := url.Parse(websiteURL) @@ -58,15 +64,15 @@ func (t *TemplateEngine) ParseAll() { return ts.Format("2006-01-02 15:04:05") }, "elapsed": func(ts time.Time) string { - return helper.GetElapsedTime(t.currentLocale, ts) + return helper.GetElapsedTime(e.currentLocale, ts) }, "t": func(key interface{}, args ...interface{}) string { switch key.(type) { case string: - return t.currentLocale.Get(key.(string), args...) + return e.currentLocale.Get(key.(string), args...) case errors.LocalizedError: err := key.(errors.LocalizedError) - return err.Localize(t.currentLocale) + return err.Localize(e.currentLocale) case error: return key.(error).Error() default: @@ -74,7 +80,7 @@ func (t *TemplateEngine) ParseAll() { } }, "plural": func(key string, n int, args ...interface{}) string { - return t.currentLocale.Plural(key, n, args...) + return e.currentLocale.Plural(key, n, args...) }, } @@ -85,16 +91,18 @@ func (t *TemplateEngine) ParseAll() { for name, content := range templateViewsMap { log.Println("Parsing template:", name) - t.templates[name] = template.Must(template.New("main").Funcs(funcMap).Parse(commonTemplates + content)) + e.templates[name] = template.Must(template.New("main").Funcs(funcMap).Parse(commonTemplates + content)) } } -func (t *TemplateEngine) SetLanguage(language string) { - t.currentLocale = t.translator.GetLanguage(language) +// SetLanguage change the language for template processing. +func (e *Engine) SetLanguage(language string) { + e.currentLocale = e.translator.GetLanguage(language) } -func (t *TemplateEngine) Execute(w io.Writer, name string, data interface{}) { - tpl, ok := t.templates[name] +// Execute process a template. +func (e *Engine) Execute(w io.Writer, name string, data interface{}) { + tpl, ok := e.templates[name] if !ok { log.Fatalf("The template %s does not exists.\n", name) } @@ -108,13 +116,15 @@ func (t *TemplateEngine) Execute(w io.Writer, name string, data interface{}) { b.WriteTo(w) } -func NewTemplateEngine(router *mux.Router, translator *locale.Translator) *TemplateEngine { - tpl := &TemplateEngine{ +// NewEngine returns a new template Engine. +func NewEngine(cfg *config.Config, router *mux.Router, translator *locale.Translator) *Engine { + tpl := &Engine{ templates: make(map[string]*template.Template), router: router, translator: translator, + cfg: cfg, } - tpl.ParseAll() + tpl.parseAll() return tpl } diff --git a/server/template/views.go b/server/template/views.go index 7f192d0..cfa571f 100644 --- a/server/template/views.go +++ b/server/template/views.go @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// 2017-11-21 15:41:59.472545112 -0800 PST m=+0.018780770 +// 2017-11-21 19:31:59.633723314 -0800 PST m=+0.012722162 package template @@ -14,6 +14,9 @@ var templateViewsMap = map[string]string{ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> {{ if .user.IsAdmin }} @@ -268,6 +271,9 @@ var templateViewsMap = map[string]string{ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> <li> @@ -402,6 +408,9 @@ var templateViewsMap = map[string]string{ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> <li> @@ -727,6 +736,42 @@ var templateViewsMap = map[string]string{ {{ end }} `, + "integrations": `{{ define "title"}}{{ t "Integrations" }}{{ end }} + +{{ define "content"}} +<section class="page-header"> + <h1>{{ t "Integrations" }}</h1> + <ul> + <li> + <a href="{{ route "settings" }}">{{ t "Settings" }}</a> + </li> + <li> + <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> + </li> + {{ if .user.IsAdmin }} + <li> + <a href="{{ route "users" }}">{{ t "Users" }}</a> + </li> + {{ end }} + <li> + <a href="{{ route "about" }}">{{ t "About" }}</a> + </li> + </ul> +</section> + +<div class="panel"> + <h3>{{ t "Bookmarklet" }}</h3> + <p>{{ t "This special link allows you to subscribe to a website directly by using a bookmark in your web browser." }}</p> + + <div class="bookmarklet"> + <a href="javascript:location.href='{{ baseURL }}{{ route "bookmarklet" }}?uri='+encodeURIComponent(window.location.href)">{{ t "Add to Miniflux" }}</a> + </div> + + <p>{{ t "Drag and drop this link to your bookmarks." }}</p> +</div> + +{{ end }} +`, "login": `{{ define "title"}}{{ t "Sign In" }}{{ end }} {{ define "content"}} @@ -761,6 +806,9 @@ var templateViewsMap = map[string]string{ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "users" }}">{{ t "Users" }}</a> </li> <li> @@ -807,6 +855,9 @@ var templateViewsMap = map[string]string{ <h1>{{ t "Settings" }}</h1> <ul> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> {{ if .user.IsAdmin }} @@ -921,6 +972,9 @@ var templateViewsMap = map[string]string{ <a href="{{ route "settings" }}">{{ t "Settings" }}</a> </li> <li> + <a href="{{ route "integrations" }}">{{ t "Integrations" }}</a> + </li> + <li> <a href="{{ route "sessions" }}">{{ t "Sessions" }}</a> </li> <li> @@ -972,24 +1026,25 @@ var templateViewsMap = map[string]string{ } var templateViewsMapChecksums = map[string]string{ - "about": "56f1d45d8b9944306c66be0712320527e739a0ce4fccbd97a4c414c8f9cfab04", + "about": "ad2fb778fc73c39b733b3f81b13e5c7d689b041fadd24ee2d4577f545aa788ad", "add_subscription": "098ea9e492e18242bd414b22c4d8638006d113f728e5ae78c9186663f60ae3f1", "categories": "ca1280cd157bb527d4fc907da67b05a8347378f6dce965b9389d4bcdf3600a11", "category_entries": "0bdcf28ef29b976b78d1add431896a8c56791476abd7a4240998d52c3efe1f35", "choose_subscription": "d37682743d8bbd84738a964e238103db2651f95fa340c6e285ffe2e12548d673", "create_category": "2b82af5d2dcd67898dc5daa57a6461e6ff8121a6089b2a2a1be909f35e4a2275", - "create_user": "966b31d0414e0d0a547ef9ada428cbd24a91100bfed491f780c0461892a2489b", + "create_user": "815dd31faaa6e9ba81a2a6664e5707aaf4153c392accd2b1f77cf1937035a881", "edit_category": "cee720faadcec58289b707ad30af623d2ee66c1ce23a732965463250d7ff41c5", "edit_feed": "c5bc4c22bf7e8348d880395250545595d21fb8c8e723fc5d7cca68e25d250884", - "edit_user": "f0f79704983de3ca7858bd8cda7a372c3999f5e4e0cf951fba5fa2c1752f9111", + "edit_user": "c835d78f7cf36c11533db9cef253457a9003987d704070d59446cb2b0e84dcb9", "entry": "32e605edd6d43773ac31329d247ebd81d38d974cd43689d91de79fffec7fe04b", "feed_entries": "9aff923b6c7452dec1514feada7e0d2bbc1ec21c6f5e9f48b2de41d1b731ffe4", "feeds": "94e43404a4044490c065c888a49bebd3ff51b588b9fb47d03c2598003aa40dca", "history": "947603cbde888516e62925f5d08fb0b13d930623d3ee4c690dbc22612fdda75e", "import": "73b5112e20bfd232bf73334544186ea419505936bc237d481517a8622901878f", + "integrations": "c485d6d9ed996635e55e73320610e6bcb01a41c1153e8e739ae2294b0b14b243", "login": "568f2f69f248048f3e55e9bbc719077a74ae23fe18f237aa40e3de37e97b7a41", - "sessions": "5ac3793f0ee74d0807bab6a173a1aa6508e98add5c022fa54c8fdf5c6b4a0e75", - "settings": "9c89bfd70ff288b4256e5205be78a7645450b364db1df51d10fee3cb915b2c6b", + "sessions": "878dbe8f8ea783b44130c495814179519fa5c3aa2666ac87508f94d58dd008bf", + "settings": "a972fb5767fd32522648149880e40607ed8bbed7a389038bbab6b08539ac2893", "unread": "b6f9be1a72188947c75a6fdcac6ff7878db7745f9efa46318e0433102892a722", - "users": "c6b1fa81cf229dde88e69c353114b542c67794a2cd513eddaf600828ab14aa18", + "users": "44677e28bb5347799ed0020c90ec785aadec4b1454446d92411cfdaf6e32110b", } diff --git a/server/ui/controller/integrations.go b/server/ui/controller/integrations.go new file mode 100644 index 0000000..887f619 --- /dev/null +++ b/server/ui/controller/integrations.go @@ -0,0 +1,20 @@ +// Copyright 2017 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package controller + +import "github.com/miniflux/miniflux2/server/core" + +// ShowIntegrations renders the page with all external integrations. +func (c *Controller) ShowIntegrations(ctx *core.Context, request *core.Request, response *core.Response) { + args, err := c.getCommonTemplateArgs(ctx) + if err != nil { + response.HTML().ServerError(err) + return + } + + response.HTML().Render("integrations", args.Merge(tplParams{ + "menu": "settings", + })) +} diff --git a/server/ui/controller/subscription.go b/server/ui/controller/subscription.go index 22ec685..342f0c5 100644 --- a/server/ui/controller/subscription.go +++ b/server/ui/controller/subscription.go @@ -5,13 +5,30 @@ package controller import ( + "log" + "github.com/miniflux/miniflux2/model" "github.com/miniflux/miniflux2/reader/subscription" "github.com/miniflux/miniflux2/server/core" "github.com/miniflux/miniflux2/server/ui/form" - "log" ) +// Bookmarklet prefill the form to add a subscription from the URL provided by the bookmarklet. +func (c *Controller) Bookmarklet(ctx *core.Context, request *core.Request, response *core.Response) { + user := ctx.LoggedUser() + args, err := c.getSubscriptionFormTemplateArgs(ctx, user) + if err != nil { + response.HTML().ServerError(err) + return + } + + bookmarkletURL := request.QueryStringParam("uri", "") + response.HTML().Render("add_subscription", args.Merge(tplParams{ + "form": &form.SubscriptionForm{URL: bookmarkletURL}, + })) +} + +// AddSubscription shows the form to add a new feed. func (c *Controller) AddSubscription(ctx *core.Context, request *core.Request, response *core.Response) { user := ctx.LoggedUser() @@ -24,6 +41,7 @@ func (c *Controller) AddSubscription(ctx *core.Context, request *core.Request, r response.HTML().Render("add_subscription", args) } +// SubmitSubscription try to find a feed from the URL provided by the user. func (c *Controller) SubmitSubscription(ctx *core.Context, request *core.Request, response *core.Response) { user := ctx.LoggedUser() @@ -80,6 +98,7 @@ func (c *Controller) SubmitSubscription(ctx *core.Context, request *core.Request } } +// ChooseSubscription shows a page to choose a subscription. func (c *Controller) ChooseSubscription(ctx *core.Context, request *core.Request, response *core.Response) { user := ctx.LoggedUser() @@ -1,5 +1,5 @@ // Code generated by go generate; DO NOT EDIT. -// 2017-11-21 15:41:59.457985225 -0800 PST m=+0.004220883 +// 2017-11-21 19:31:59.625242989 -0800 PST m=+0.004241837 package sql |