diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index a2de7b7..79ce26f 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -6,11 +6,19 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> + + + <%= yield :meta %> <%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %> <%= javascript_importmap_tags %> + diff --git a/app/views/layouts/devise.html.erb b/app/views/layouts/devise.html.erb index 39bc8e3..4d99238 100644 --- a/app/views/layouts/devise.html.erb +++ b/app/views/layouts/devise.html.erb @@ -6,10 +6,18 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> + + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %> <%= javascript_importmap_tags %> + diff --git a/app/views/layouts/home.html.erb b/app/views/layouts/home.html.erb index 2af0ca8..81e39d7 100644 --- a/app/views/layouts/home.html.erb +++ b/app/views/layouts/home.html.erb @@ -6,10 +6,18 @@ <%= csrf_meta_tags %> <%= csp_meta_tag %> + + + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbo-track': 'reload' %> <%= javascript_importmap_tags %> + diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png index e69de29..75b97aa 100644 Binary files a/public/apple-touch-icon-precomposed.png and b/public/apple-touch-icon-precomposed.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png index e69de29..75b97aa 100644 Binary files a/public/apple-touch-icon.png and b/public/apple-touch-icon.png differ diff --git a/public/icons/icon-192x192.png b/public/icons/icon-192x192.png new file mode 100644 index 0000000..c74857c Binary files /dev/null and b/public/icons/icon-192x192.png differ diff --git a/public/icons/icon-512x512.png b/public/icons/icon-512x512.png new file mode 100644 index 0000000..849673b Binary files /dev/null and b/public/icons/icon-512x512.png differ diff --git a/public/icons/icon-dark-192x192.png b/public/icons/icon-dark-192x192.png new file mode 100644 index 0000000..f58310a Binary files /dev/null and b/public/icons/icon-dark-192x192.png differ diff --git a/public/icons/icon-dark-512x512.png b/public/icons/icon-dark-512x512.png new file mode 100644 index 0000000..febf99f Binary files /dev/null and b/public/icons/icon-dark-512x512.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..0634006 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,27 @@ +{ + "name": "Draft", + "short_name": "Draft", + "description": "A focused writing application", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#ffffff", + "icons": [ + { + "src": "/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/public/offline.html b/public/offline.html new file mode 100644 index 0000000..0dcf57f --- /dev/null +++ b/public/offline.html @@ -0,0 +1,32 @@ + + + + + + Draft - Offline + + + +
+

You're offline

+

Check your connection and try again.

+
+ + diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..c52dd9b --- /dev/null +++ b/public/sw.js @@ -0,0 +1,45 @@ +const CACHE_NAME = "draft-v1"; +const OFFLINE_URL = "/offline.html"; + +const PRECACHE_URLS = [ + "/offline.html" +]; + +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS)) + ); + self.skipWaiting(); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key))) + ) + ); + self.clients.claim(); +}); + +self.addEventListener("fetch", (event) => { + if (event.request.mode === "navigate") { + event.respondWith( + fetch(event.request).catch(() => caches.match(OFFLINE_URL)) + ); + return; + } + + event.respondWith( + caches.match(event.request).then((cached) => { + if (cached) return cached; + return fetch(event.request).then((response) => { + if (!response || response.status !== 200 || response.type !== "basic") { + return response; + } + const toCache = response.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(event.request, toCache)); + return response; + }); + }) + ); +}); diff --git a/test/integration/pwa_test.rb b/test/integration/pwa_test.rb new file mode 100644 index 0000000..f487ab5 --- /dev/null +++ b/test/integration/pwa_test.rb @@ -0,0 +1,48 @@ +require 'test_helper' + +class PwaTest < ActionDispatch::IntegrationTest + include Devise::Test::IntegrationHelpers + + test 'manifest.json is served with correct content type' do + get '/manifest.json' + assert_response :success + manifest = JSON.parse(response.body) + assert_equal 'Draft', manifest['name'] + assert_equal 'standalone', manifest['display'] + assert manifest['icons'].any? { |icon| icon['sizes'] == '512x512' } + end + + test 'service worker is served' do + get '/sw.js' + assert_response :success + assert_includes response.body, 'CACHE_NAME' + end + + test 'offline page is served' do + get '/offline.html' + assert_response :success + assert_includes response.body, "You're offline" + end + + test 'home layout includes PWA tags' do + sign_in users(:bob) + get root_path + assert_response :success + + assert_select 'link[rel="manifest"][href="/manifest.json"]' + assert_select 'meta[name="theme-color"]' + assert_select 'link[rel="apple-touch-icon"]' + assert_includes response.body, 'navigator.serviceWorker.register' + end + + test 'application layout includes PWA tags' do + sign_in users(:bob) + get stories_path + assert_response :success + + assert_select 'link[rel="manifest"][href="/manifest.json"]' + assert_select 'meta[name="theme-color"]' + assert_select 'link[rel="apple-touch-icon"]' + assert_includes response.body, 'navigator.serviceWorker.register' + end +end