Add service worker to cache feed icons
This commit is contained in:
parent
c926498d3d
commit
6aa02680d8
8 changed files with 94 additions and 35 deletions
|
@ -84,7 +84,7 @@ func routes(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handle
|
|||
uiRouter.Use(middleware.AppSession)
|
||||
uiRouter.Use(middleware.UserSession)
|
||||
uiRouter.HandleFunc("/stylesheets/{name}.css", uiController.Stylesheet).Name("stylesheet").Methods("GET")
|
||||
uiRouter.HandleFunc("/js", uiController.Javascript).Name("javascript").Methods("GET")
|
||||
uiRouter.HandleFunc("/{name}.js", uiController.Javascript).Name("javascript").Methods("GET")
|
||||
uiRouter.HandleFunc("/favicon.ico", uiController.Favicon).Name("favicon").Methods("GET")
|
||||
uiRouter.HandleFunc("/icon/{filename}", uiController.AppIcon).Name("appIcon").Methods("GET")
|
||||
uiRouter.HandleFunc("/manifest.json", uiController.WebManifest).Name("webManifest").Methods("GET")
|
||||
|
|
72
generate.go
72
generate.go
|
@ -96,23 +96,33 @@ func concat(files []string) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
func generateJSBundle(bundleFile string, srcFiles []string) {
|
||||
var b strings.Builder
|
||||
b.WriteString("(function() {'use strict';")
|
||||
b.WriteString(concat(srcFiles))
|
||||
b.WriteString("})();")
|
||||
|
||||
func generateJSBundle(bundleFile string, bundleFiles map[string][]string, prefixes, suffixes map[string]string) {
|
||||
bundle := NewBundle("static", "Javascripts")
|
||||
m := minify.New()
|
||||
m.AddFunc("text/javascript", js.Minify)
|
||||
|
||||
output, err := m.String("text/javascript", b.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
for name, srcFiles := range bundleFiles {
|
||||
var b strings.Builder
|
||||
|
||||
if prefix, found := prefixes[name]; found {
|
||||
b.WriteString(prefix)
|
||||
}
|
||||
|
||||
b.WriteString(concat(srcFiles))
|
||||
|
||||
if suffix, found := suffixes[name]; found {
|
||||
b.WriteString(suffix)
|
||||
}
|
||||
|
||||
minifiedData, err := m.String("text/javascript", b.String())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bundle.Files[name] = minifiedData
|
||||
bundle.Checksums[name] = checksum([]byte(minifiedData))
|
||||
}
|
||||
|
||||
bundle := NewBundle("static", "Javascript")
|
||||
bundle.Files["app"] = output
|
||||
bundle.Checksums["app"] = checksum([]byte(output))
|
||||
bundle.Write(bundleFile)
|
||||
}
|
||||
|
||||
|
@ -165,20 +175,30 @@ func generateBundle(bundleFile, pkg, mapName string, srcFiles []string) {
|
|||
}
|
||||
|
||||
func main() {
|
||||
generateJSBundle("ui/static/js.go", []string{
|
||||
"ui/static/js/dom_helper.js",
|
||||
"ui/static/js/touch_handler.js",
|
||||
"ui/static/js/keyboard_handler.js",
|
||||
"ui/static/js/mouse_handler.js",
|
||||
"ui/static/js/form_handler.js",
|
||||
"ui/static/js/request_builder.js",
|
||||
"ui/static/js/unread_counter_handler.js",
|
||||
"ui/static/js/entry_handler.js",
|
||||
"ui/static/js/confirm_handler.js",
|
||||
"ui/static/js/menu_handler.js",
|
||||
"ui/static/js/modal_handler.js",
|
||||
"ui/static/js/nav_handler.js",
|
||||
"ui/static/js/bootstrap.js",
|
||||
generateJSBundle("ui/static/js.go", map[string][]string{
|
||||
"app": []string{
|
||||
"ui/static/js/dom_helper.js",
|
||||
"ui/static/js/touch_handler.js",
|
||||
"ui/static/js/keyboard_handler.js",
|
||||
"ui/static/js/mouse_handler.js",
|
||||
"ui/static/js/form_handler.js",
|
||||
"ui/static/js/request_builder.js",
|
||||
"ui/static/js/unread_counter_handler.js",
|
||||
"ui/static/js/entry_handler.js",
|
||||
"ui/static/js/confirm_handler.js",
|
||||
"ui/static/js/menu_handler.js",
|
||||
"ui/static/js/modal_handler.js",
|
||||
"ui/static/js/nav_handler.js",
|
||||
"ui/static/js/bootstrap.js",
|
||||
},
|
||||
"sw": []string{
|
||||
"ui/static/js/sw.js",
|
||||
},
|
||||
}, map[string]string{
|
||||
"app": "(function(){'use strict';",
|
||||
"sw": "'use strict';",
|
||||
}, map[string]string{
|
||||
"app": "})();",
|
||||
})
|
||||
|
||||
generateCSSBundle("ui/static/css.go", map[string][]string{
|
||||
|
|
|
@ -103,7 +103,9 @@ var templateCommonMap = map[string]string{
|
|||
{{ else }}
|
||||
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" "default" }}">
|
||||
{{ end }}
|
||||
<script type="text/javascript" src="{{ route "javascript" }}" defer></script>
|
||||
|
||||
<script type="text/javascript" src="{{ route "javascript" "name" "app" }}" defer></script>
|
||||
<script type="text/javascript" src="{{ route "javascript" "name" "sw" }}" defer id="service-worker-script"></script>
|
||||
</head>
|
||||
<body data-entries-status-url="{{ route "updateEntriesStatus" }}">
|
||||
{{ if .user }}
|
||||
|
@ -232,6 +234,6 @@ var templateCommonMap = map[string]string{
|
|||
var templateCommonMapChecksums = map[string]string{
|
||||
"entry_pagination": "756ef122f3ebc73754b5fc4304bf05e59da0ab4af030b2509ff4c9b4a74096ce",
|
||||
"item_meta": "6cff8ae243f19dac936e523867d2975f70aa749b2a461ae63f6ebbca94cf7419",
|
||||
"layout": "27b21c2d8ce341edcc11bf557f73fcd08dad1dfc8d0788c94addbc87f94f2f19",
|
||||
"layout": "7a4a1ec5fdfe96b20626aa7b9028fd1f166a753e46b497f6d2232f5cd050895d",
|
||||
"pagination": "b592d58ea9d6abf2dc0b158621404cbfaeea5413b1c8b8b9818725963096b196",
|
||||
}
|
||||
|
|
|
@ -29,7 +29,9 @@
|
|||
{{ else }}
|
||||
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" "default" }}">
|
||||
{{ end }}
|
||||
<script type="text/javascript" src="{{ route "javascript" }}" defer></script>
|
||||
|
||||
<script type="text/javascript" src="{{ route "javascript" "name" "app" }}" defer></script>
|
||||
<script type="text/javascript" src="{{ route "javascript" "name" "sw" }}" defer id="service-worker-script"></script>
|
||||
</head>
|
||||
<body data-entries-status-url="{{ route "updateEntriesStatus" }}">
|
||||
{{ if .user }}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
package static
|
||||
|
||||
var Javascript = map[string]string{
|
||||
var Javascripts = map[string]string{
|
||||
"app": `(function(){'use strict';class DomHelper{static isVisible(element){return element.offsetParent!==null;}
|
||||
static openNewTab(url){let win=window.open("");win.opener=null;win.location=url;win.focus();}
|
||||
static scrollPageTo(element){let windowScrollPosition=window.pageYOffset;let windowHeight=document.documentElement.clientHeight;let viewportPosition=windowScrollPosition+windowHeight;let itemBottomPosition=element.offsetTop+element.offsetHeight;if(viewportPosition-itemBottomPosition<0||viewportPosition-element.offsetTop>windowHeight){window.scrollTo(0,element.offsetTop-10);}}
|
||||
|
@ -91,9 +91,12 @@ if(currentItem===null){items[0].classList.add("current-item");return;}
|
|||
for(let i=0;i<items.length;i++){if(items[i].classList.contains("current-item")){items[i].classList.remove("current-item");if(i+1<items.length){items[i+1].classList.add("current-item");DomHelper.scrollPageTo(items[i+1]);}
|
||||
break;}}}
|
||||
isListView(){return document.querySelector(".items")!==null;}}
|
||||
document.addEventListener("DOMContentLoaded",function(){FormHandler.handleSubmitButtons();let touchHandler=new TouchHandler();touchHandler.listen();let navHandler=new NavHandler();let keyboardHandler=new KeyboardHandler();keyboardHandler.on("g u",()=>navHandler.goToPage("unread"));keyboardHandler.on("g b",()=>navHandler.goToPage("starred"));keyboardHandler.on("g h",()=>navHandler.goToPage("history"));keyboardHandler.on("g f",()=>navHandler.goToPage("feeds"));keyboardHandler.on("g c",()=>navHandler.goToPage("categories"));keyboardHandler.on("g s",()=>navHandler.goToPage("settings"));keyboardHandler.on("ArrowLeft",()=>navHandler.goToPrevious());keyboardHandler.on("ArrowRight",()=>navHandler.goToNext());keyboardHandler.on("j",()=>navHandler.goToPrevious());keyboardHandler.on("p",()=>navHandler.goToPrevious());keyboardHandler.on("k",()=>navHandler.goToNext());keyboardHandler.on("n",()=>navHandler.goToNext());keyboardHandler.on("h",()=>navHandler.goToPage("previous"));keyboardHandler.on("l",()=>navHandler.goToPage("next"));keyboardHandler.on("o",()=>navHandler.openSelectedItem());keyboardHandler.on("v",()=>navHandler.openOriginalLink());keyboardHandler.on("m",()=>navHandler.toggleEntryStatus());keyboardHandler.on("A",()=>navHandler.markPageAsRead());keyboardHandler.on("s",()=>navHandler.saveEntry());keyboardHandler.on("d",()=>navHandler.fetchOriginalContent());keyboardHandler.on("f",()=>navHandler.toggleBookmark());keyboardHandler.on("?",()=>navHandler.showKeyboardShortcuts());keyboardHandler.on("/",(e)=>navHandler.setFocusToSearchInput(e));keyboardHandler.on("Escape",()=>ModalHandler.close());keyboardHandler.listen();let mouseHandler=new MouseHandler();mouseHandler.onClick("a[data-save-entry]",(event)=>{event.preventDefault();EntryHandler.saveEntry(event.target);});mouseHandler.onClick("a[data-toggle-bookmark]",(event)=>{event.preventDefault();EntryHandler.toggleBookmark(event.target);});mouseHandler.onClick("a[data-toggle-status]",(event)=>{event.preventDefault();let currentItem=DomHelper.findParent(event.target,"item");if(currentItem){EntryHandler.toggleEntryStatus(currentItem);}});mouseHandler.onClick("a[data-fetch-content-entry]",(event)=>{event.preventDefault();EntryHandler.fetchOriginalContent(event.target);});mouseHandler.onClick("a[data-on-click=markPageAsRead]",()=>navHandler.markPageAsRead());mouseHandler.onClick("a[data-confirm]",(event)=>{(new ConfirmHandler()).handle(event);});mouseHandler.onClick("a[data-action=search]",(event)=>{navHandler.setFocusToSearchInput(event);});if(document.documentElement.clientWidth<600){let menuHandler=new MenuHandler();mouseHandler.onClick(".logo",()=>menuHandler.toggleMainMenu());mouseHandler.onClick(".header nav li",(event)=>menuHandler.clickMenuListItem(event));}});})();`,
|
||||
document.addEventListener("DOMContentLoaded",function(){FormHandler.handleSubmitButtons();let touchHandler=new TouchHandler();touchHandler.listen();let navHandler=new NavHandler();let keyboardHandler=new KeyboardHandler();keyboardHandler.on("g u",()=>navHandler.goToPage("unread"));keyboardHandler.on("g b",()=>navHandler.goToPage("starred"));keyboardHandler.on("g h",()=>navHandler.goToPage("history"));keyboardHandler.on("g f",()=>navHandler.goToPage("feeds"));keyboardHandler.on("g c",()=>navHandler.goToPage("categories"));keyboardHandler.on("g s",()=>navHandler.goToPage("settings"));keyboardHandler.on("ArrowLeft",()=>navHandler.goToPrevious());keyboardHandler.on("ArrowRight",()=>navHandler.goToNext());keyboardHandler.on("j",()=>navHandler.goToPrevious());keyboardHandler.on("p",()=>navHandler.goToPrevious());keyboardHandler.on("k",()=>navHandler.goToNext());keyboardHandler.on("n",()=>navHandler.goToNext());keyboardHandler.on("h",()=>navHandler.goToPage("previous"));keyboardHandler.on("l",()=>navHandler.goToPage("next"));keyboardHandler.on("o",()=>navHandler.openSelectedItem());keyboardHandler.on("v",()=>navHandler.openOriginalLink());keyboardHandler.on("m",()=>navHandler.toggleEntryStatus());keyboardHandler.on("A",()=>navHandler.markPageAsRead());keyboardHandler.on("s",()=>navHandler.saveEntry());keyboardHandler.on("d",()=>navHandler.fetchOriginalContent());keyboardHandler.on("f",()=>navHandler.toggleBookmark());keyboardHandler.on("?",()=>navHandler.showKeyboardShortcuts());keyboardHandler.on("/",(e)=>navHandler.setFocusToSearchInput(e));keyboardHandler.on("Escape",()=>ModalHandler.close());keyboardHandler.listen();let mouseHandler=new MouseHandler();mouseHandler.onClick("a[data-save-entry]",(event)=>{event.preventDefault();EntryHandler.saveEntry(event.target);});mouseHandler.onClick("a[data-toggle-bookmark]",(event)=>{event.preventDefault();EntryHandler.toggleBookmark(event.target);});mouseHandler.onClick("a[data-toggle-status]",(event)=>{event.preventDefault();let currentItem=DomHelper.findParent(event.target,"item");if(currentItem){EntryHandler.toggleEntryStatus(currentItem);}});mouseHandler.onClick("a[data-fetch-content-entry]",(event)=>{event.preventDefault();EntryHandler.fetchOriginalContent(event.target);});mouseHandler.onClick("a[data-on-click=markPageAsRead]",()=>navHandler.markPageAsRead());mouseHandler.onClick("a[data-confirm]",(event)=>{(new ConfirmHandler()).handle(event);});mouseHandler.onClick("a[data-action=search]",(event)=>{navHandler.setFocusToSearchInput(event);});if(document.documentElement.clientWidth<600){let menuHandler=new MenuHandler();mouseHandler.onClick(".logo",()=>menuHandler.toggleMainMenu());mouseHandler.onClick(".header nav li",(event)=>menuHandler.clickMenuListItem(event));}
|
||||
if("serviceWorker"in navigator){let scriptElement=document.getElementById("service-worker-script");if(scriptElement){navigator.serviceWorker.register(scriptElement.src);}}});})();`,
|
||||
"sw": `'use strict';self.addEventListener("fetch",(event)=>{if(event.request.url.includes("/feed/icon/")){event.respondWith(caches.open("feed_icons").then((cache)=>{return cache.match(event.request).then((response)=>{return response||fetch(event.request).then((response)=>{cache.put(event.request,response.clone());return response;});});}));}});`,
|
||||
}
|
||||
|
||||
var JavascriptChecksums = map[string]string{
|
||||
"app": "ef0457f301b924c25a20fa00e65bfda5437f41bc9ad0a0762b62b9c64b6d0ac5",
|
||||
var JavascriptsChecksums = map[string]string{
|
||||
"app": "cf86a525349b18a448c8599978c9f54cb41092128c5c573b40cbb22a588ac24e",
|
||||
"sw": "55fffa223919cc18572788fb9c62fccf92166c0eb5d3a1d6f91c31f24d020be9",
|
||||
}
|
||||
|
|
7
ui/static/js/bootstrap.js
vendored
7
ui/static/js/bootstrap.js
vendored
|
@ -71,4 +71,11 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
mouseHandler.onClick(".logo", () => menuHandler.toggleMainMenu());
|
||||
mouseHandler.onClick(".header nav li", (event) => menuHandler.clickMenuListItem(event));
|
||||
}
|
||||
|
||||
if ("serviceWorker" in navigator) {
|
||||
let scriptElement = document.getElementById("service-worker-script");
|
||||
if (scriptElement) {
|
||||
navigator.serviceWorker.register(scriptElement.src);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
14
ui/static/js/sw.js
Normal file
14
ui/static/js/sw.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.url.includes("/feed/icon/")) {
|
||||
event.respondWith(
|
||||
caches.open("feed_icons").then((cache) => {
|
||||
return cache.match(event.request).then((response) => {
|
||||
return response || fetch(event.request).then((response) => {
|
||||
cache.put(event.request, response.clone());
|
||||
return response;
|
||||
});
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
|
@ -8,11 +8,22 @@ import (
|
|||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/miniflux/miniflux/http/request"
|
||||
"github.com/miniflux/miniflux/http/response"
|
||||
"github.com/miniflux/miniflux/http/response/html"
|
||||
"github.com/miniflux/miniflux/ui/static"
|
||||
)
|
||||
|
||||
// Javascript renders application client side code.
|
||||
func (c *Controller) Javascript(w http.ResponseWriter, r *http.Request) {
|
||||
response.Cache(w, r, "text/javascript; charset=utf-8", static.JavascriptChecksums["app"], []byte(static.Javascript["app"]), 48*time.Hour)
|
||||
filename := request.Param(r, "name", "app")
|
||||
if _, found := static.Javascripts[filename]; !found {
|
||||
html.NotFound(w)
|
||||
return
|
||||
}
|
||||
|
||||
body := static.Javascripts[filename]
|
||||
etag := static.JavascriptsChecksums[filename]
|
||||
|
||||
response.Cache(w, r, "text/javascript; charset=utf-8", etag, []byte(body), 48*time.Hour)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue