Migrate to streaming app shell via serviceworkers (#5020) [deploy]
* Initial work for streaming app shell way of using serviceworkers * Close in on serviceworker adjustment finalization * Add docs and loading indicator * Remove useless code * Don't run on API * Update app/javascript/contentDisplayPolicy/hideBlockedContent.js * Fix small details * Don't run serviceworker.js code in test env * Fix JS in serviceworker * Move private keyword to proper place in async controller * Change shell_version to HEROKU_SLUG_COMMIT * Update caching config * Add test for async_info/shell_version
This commit is contained in:
parent
70e1ffbbc3
commit
630c1b7425
32 changed files with 404 additions and 342 deletions
|
|
@ -267,6 +267,7 @@ body.trusted-status-true .trusted-visible-block {
|
|||
color: white;
|
||||
display: inline-block;
|
||||
margin-bottom: 0px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 22px;
|
||||
|
|
@ -281,4 +282,34 @@ body.trusted-status-true .trusted-visible-block {
|
|||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
form button {
|
||||
background: $red;
|
||||
color: white;
|
||||
font-size: 1.5em;
|
||||
border: 0px;
|
||||
border-radius: 3px;
|
||||
padding: 6px 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.app-shell-loader {
|
||||
position: absolute;
|
||||
top: 200px;
|
||||
text-align: center;
|
||||
left: 0;
|
||||
right: 0;
|
||||
opacity: 0;
|
||||
animation: loading-fadein 1.5s;
|
||||
animation-delay: 0.6s;
|
||||
font-size: 1.2em;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.base-background-color {
|
||||
@include themeable(background, theme-background, $lightest-gray);
|
||||
}
|
||||
|
||||
@keyframes loading-fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 0.6; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -670,6 +670,13 @@
|
|||
.signout_confirm-wrapper {
|
||||
padding: calc(15% + 50px) 2% 30%;
|
||||
text-align: center;
|
||||
form button {
|
||||
font-size: 1.5em;
|
||||
padding: 8px 15px;
|
||||
border: 0px;
|
||||
border-radius: 3px;
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.org-widget-preview .primary-sticky-nav {
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class ApplicationController < ActionController::Base
|
|||
def after_sign_in_path_for(resource)
|
||||
return "/onboarding?referrer=#{request.env['omniauth.origin'] || 'none'}" unless current_user.saw_onboarding
|
||||
|
||||
request.env["omniauth.origin"] || stored_location_for(resource) || "/dashboard"
|
||||
(request.env["omniauth.origin"] || stored_location_for(resource) || "/dashboard") + "?signin=true" # This signin=true param is used by frontend
|
||||
end
|
||||
|
||||
def raise_banned
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
class AsyncInfoController < ApplicationController
|
||||
include Devise::Controllers::Rememberable
|
||||
# No pundit policy. All actions are unrestricted.
|
||||
before_action :set_cache_control_headers, only: %i[shell_version]
|
||||
|
||||
def base_data
|
||||
flash.discard(:notice)
|
||||
|
|
@ -29,6 +30,13 @@ class AsyncInfoController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def shell_version
|
||||
set_surrogate_key_header "shell-version-endpoint"
|
||||
# shell_version will change on every deploy. *Technically* could be only on changes to assets and shell, but this is more fool-proof.
|
||||
shell_version = ApplicationConfig["HEROKU_SLUG_COMMIT"]
|
||||
render json: { version: Rails.env.production? ? shell_version : rand(1000) }.to_json
|
||||
end
|
||||
|
||||
def user_data
|
||||
Rails.cache.fetch(user_cache_key, expires_in: 15.minutes) do
|
||||
{
|
||||
|
|
|
|||
13
app/controllers/shell_controller.rb
Normal file
13
app/controllers/shell_controller.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class ShellController < ApplicationController
|
||||
layout false
|
||||
|
||||
def top
|
||||
@shell = true
|
||||
render partial: "top"
|
||||
end
|
||||
|
||||
def bottom
|
||||
@shell = true
|
||||
render partial: "bottom"
|
||||
end
|
||||
end
|
||||
|
|
@ -15,38 +15,6 @@ module ApplicationHelper
|
|||
end
|
||||
end
|
||||
|
||||
def core_pages?
|
||||
%w[
|
||||
articles
|
||||
podcast_episodes
|
||||
events
|
||||
tags
|
||||
registrations
|
||||
users
|
||||
pages
|
||||
chat_channels
|
||||
dashboards
|
||||
moderations
|
||||
videos
|
||||
badges
|
||||
stories
|
||||
comments
|
||||
notifications
|
||||
reading_list_items
|
||||
html_variants
|
||||
classified_listings
|
||||
credits
|
||||
partnerships
|
||||
pro_memberships
|
||||
].include?(controller_name)
|
||||
end
|
||||
|
||||
def render_js?
|
||||
article_pages = controller_name == "articles" && %(index show).include?(controller.action_name)
|
||||
pulses_pages = controller_name == "pulses"
|
||||
!(article_pages || pulses_pages)
|
||||
end
|
||||
|
||||
def title(page_title)
|
||||
derived_title = if page_title.include?(ApplicationConfig["COMMUNITY_NAME"])
|
||||
page_title
|
||||
|
|
|
|||
|
|
@ -88,6 +88,9 @@ export default class Chat extends Component {
|
|||
channel => `open-channel-${channel.chat_channel_id}`,
|
||||
);
|
||||
setupObserver(this.observerCallback);
|
||||
if (!window.currentUser) {
|
||||
window.currentUser = JSON.parse(document.body.dataset.user)
|
||||
}
|
||||
this.subscribePusher(
|
||||
`private-message-notifications-${window.currentUser.id}`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ export default function hideBlockedContent() {
|
|||
const contentUserElements = Array.from(
|
||||
document.querySelectorAll('div[data-content-user-id]'),
|
||||
);
|
||||
/* eslint-disable-next-line no-undef */
|
||||
const blockedUserIds = userData().blocked_user_ids;
|
||||
const user = userData(); //global var
|
||||
const blockedUserIds = user ? user.blocked_user_ids : [];
|
||||
|
||||
const divsToHide = contentUserElements.filter(div => {
|
||||
const { contentUserId } = div.dataset;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
<%= javascript_include_tag "application" %>
|
||||
|
||||
<div class="container delete-confirm">
|
||||
<h4><%= @article.title %></h4>
|
||||
<h1>Are you sure you want to delete this article?</h1>
|
||||
|
|
@ -11,8 +9,7 @@
|
|||
</a>
|
||||
instead?
|
||||
</h2>
|
||||
|
||||
<h2>
|
||||
<a class="delete-link" data-no-instant rel="nofollow" data-method="delete" href="/articles/<%= @article.id %>">DELETE</a>
|
||||
</h2>
|
||||
<%= form_tag "/articles/#{@article.id}", method: :delete do %>
|
||||
<button class="cta">DELETE</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@
|
|||
<% end %>
|
||||
<%= javascript_pack_tag "homePage", defer: true %>
|
||||
|
||||
<% params.delete(:i) %>
|
||||
<% cache("main-stories-index-#{params}-#{user_signed_in?}", expires_in: 3.minutes) do %>
|
||||
<div class="home" id="index-container"
|
||||
data-params="<%= params.to_json(only: %i[tag username q]) %>" data-which="<%= @list_of %>"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<%= content_for :page_meta do %>
|
||||
<%= render "articles/tags/meta" %>
|
||||
<% end %>
|
||||
<% params.delete(:i) %>
|
||||
<% expiry_minutes = params[:timeframe].blank? || params[:timeframe] == "latest" ? 4 : 20 %>
|
||||
<% flag = true %>
|
||||
<% cache("tag-stories-index-#{params}-#{flag}-#{@tag_model.updated_at}", expires_in: expiry_minutes.minutes) do %>
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
instead?
|
||||
</h2>
|
||||
|
||||
<h2>
|
||||
<a class="delete-link" data-no-instant rel="nofollow" data-method="delete" href="/listings/<%= @classified_listing.id %>">DELETE</a>
|
||||
</h2>
|
||||
<%= form_tag "/listings/#{@classified_listing.id}", method: :delete do %>
|
||||
<button class="cta">DELETE</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
<%= javascript_include_tag "application" %>
|
||||
|
||||
<div class="container delete-confirm">
|
||||
|
||||
<h4><%= truncate(strip_tags(@comment.processed_html.html_safe), length: 80) %></h4>
|
||||
|
|
@ -7,8 +5,8 @@
|
|||
<h2>You cannot undo this action, perhaps you just want to
|
||||
<a data-no-instant href="<%= @comment.path %>/edit">EDIT</a> instead?</h2>
|
||||
|
||||
<h2>
|
||||
<a class="delete-link" data-no-instant rel="nofollow" data-method="delete" href="/comments/<%= @comment.id %>">DELETE</a>
|
||||
</h2>
|
||||
<%= form_tag "/comments/#{@comment.id}", method: :delete do %>
|
||||
<button class="cta">DELETE</button>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
Key Links
|
||||
</div>
|
||||
</a>
|
||||
<a href="/signout_confirm" data-no-instant id="last-nav-link">
|
||||
<a href="/signout_confirm" id="last-nav-link">
|
||||
<div class="option">
|
||||
Sign Out
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,10 @@
|
|||
<% cache "base_inline_styles_#{@story_show}_#{@article_index}_#{@home_page}_#{@article_show}_#{view_class}_#{@notifications_index}_#{core_pages?}_#{@tags_index}_#{@reading_list_items_index}_#{@history_index}_#{ApplicationConfig['HEROKU_SLUG_COMMIT']}_#{user_signed_in?}", expires_in: 8.hours do %>
|
||||
<% if @story_show %>
|
||||
<% cache "base_inline_styles_#{@story_show}_#{@article_index}_#{@home_page}_#{@article_show}_#{view_class}_#{@notifications_index}_#{@tags_index}_#{@reading_list_items_index}_#{@history_index}_#{ApplicationConfig['HEROKU_SLUG_COMMIT']}_#{user_signed_in?}_#{@shell}", expires_in: 8.hours do %>
|
||||
<% if @shell %>
|
||||
<style>
|
||||
<% Rails.application.config.assets.compile = true %>
|
||||
<%= Rails.application.assets["minimal.css"].to_s.html_safe %>
|
||||
</style>
|
||||
<% elsif @story_show %>
|
||||
<style>
|
||||
<% Rails.application.config.assets.compile = true %>
|
||||
<%= Rails.application.assets["scaffolds.css"].to_s.html_safe %>
|
||||
|
|
@ -100,9 +105,7 @@
|
|||
<style>
|
||||
<%= Rails.application.assets["badges.css"].to_s.html_safe %>
|
||||
</style>
|
||||
<% elsif core_pages? %>
|
||||
<%= stylesheet_link_tag "minimal", media: "all" %>
|
||||
<% else %>
|
||||
<%= stylesheet_link_tag "application", media: "all" %>
|
||||
<%= stylesheet_link_tag "minimal", media: "all" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -1,110 +1,13 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<% title = yield(:title) %>
|
||||
<title><%= title || "#{ApplicationConfig['COMMUNITY_NAME']} Community" %></title>
|
||||
<% if internal_navigation? %>
|
||||
<style>
|
||||
.universal-page-content-wrapper {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
<% else %>
|
||||
<meta name="last-updated" content="<%= Time.current %>">
|
||||
<meta name="user-signed-in" content="<%= user_signed_in? %>">
|
||||
<meta name="algolia-public-id" content="<%= ApplicationConfig["ALGOLIASEARCH_APPLICATION_ID"] %>">
|
||||
<meta name="algolia-public-key" content="<%= ALGOLIASEARCH_PUBLIC_SEARCH_ONLY_KEY %>">
|
||||
<meta name="environment" content="<%= Rails.env %>">
|
||||
<%= render "layouts/styles" %>
|
||||
<style>
|
||||
.home {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
min-height: 440px;
|
||||
margin: auto;
|
||||
max-width: 1250px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.home {
|
||||
margin-top: 26px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<%= javascript_pack_tag "manifest", defer: true %>
|
||||
<%= javascript_pack_tag "vendor", defer: true %>
|
||||
<%= javascript_pack_tag "Search", defer: true %>
|
||||
<% if core_pages? %>
|
||||
<%= javascript_include_tag "base", defer: true %>
|
||||
<% if user_signed_in? %>
|
||||
<%= javascript_pack_tag "onboardingRedirectCheck", defer: true %>
|
||||
<%= javascript_pack_tag "contentDisplayPolicy", defer: true %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if !core_pages? %>
|
||||
<%= stylesheet_link_tag "application", media: "all" %>
|
||||
<% if render_js? %>
|
||||
<link href='https://fonts.googleapis.com/css?family=Roboto' rel='stylesheet' type='text/css'>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if view_class == "users users-signout_confirm" %>
|
||||
<%= javascript_include_tag "application" %>
|
||||
<% end %>
|
||||
<%= yield(:page_meta) %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<%= favicon_link_tag ApplicationConfig["FAVICON_URL"] %>
|
||||
<link rel="apple-touch-icon" href="<%= asset_path "apple-icon.png" %>">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= asset_path "apple-icon-152x152.png" %>">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= asset_path "apple-icon-180x180.png" %>">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= asset_path "apple-icon-167x167.png" %>">
|
||||
<link href="<%= asset_path "android-icon-192x192.png" %>" rel="icon" sizes="192x192" />
|
||||
<link href="<%= asset_path "android-icon-128x128.png" %>" rel="icon" sizes="128x128" />
|
||||
<meta name="apple-mobile-web-app-title" content="dev.to">
|
||||
<meta name="application-name" content="dev.to">
|
||||
<meta property="fb:pages" content="568966383279687" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<% if view_class.include? "users-signout_confirm" %>
|
||||
<%= csrf_meta_tags %>
|
||||
<% end %>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="search" href="https://dev.to/search.xml" type="application/opensearchdescription+xml" title="<%= community_qualified_name %>" />
|
||||
<% end %>
|
||||
</head>
|
||||
<body data-user-status="<%= user_logged_in_status %>" data-pusher-key="<%= ApplicationConfig["PUSHER_KEY"] %>">
|
||||
<div id="body-styles"></div>
|
||||
<% if user_signed_in? && !internal_navigation? %>
|
||||
<%= render "layouts/user_config" %>
|
||||
<% end %>
|
||||
<% unless internal_navigation? %>
|
||||
<div id="audiocontent">
|
||||
<%= yield(:audio) %>
|
||||
</div>
|
||||
<% end %>
|
||||
<% cache("application-top-bar--#{user_signed_in?}--#{internal_navigation?}", expires_in: 100.hours) do %>
|
||||
<%= render "layouts/top_bar" unless internal_navigation? %>
|
||||
<% end %>
|
||||
<div id="message-notice"></div>
|
||||
<div id="page-content" class="universal-page-content-wrapper <%= view_class %>" data-current-page="<%= current_page %>">
|
||||
<% if flash[:global_notice] %>
|
||||
<div class="global-notice">
|
||||
<%= flash[:global_notice] %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div id="page-content-inner">
|
||||
<%= yield %>
|
||||
</div>
|
||||
<%= render "shell/top" %>
|
||||
<style>.app-shell-loader {display: none;}</style>
|
||||
<div id="page-content" class="universal-page-content-wrapper <%= view_class %>" data-current-page="<%= current_page %>">
|
||||
<% if flash[:global_notice] %>
|
||||
<div class="global-notice">
|
||||
<%= flash[:global_notice] %>
|
||||
</div>
|
||||
<% unless internal_navigation? %>
|
||||
<% cache("footer-and-signup-modal--#{user_signed_in?}--#{ApplicationConfig['HEROKU_SLUG_COMMIT']}", expires_in: 24.hours) do %>
|
||||
<%= render "layouts/footer" %>
|
||||
<%= render "layouts/signup_modal" unless user_signed_in? %>
|
||||
<% end %>
|
||||
<div id="live-article-indicator" class="live-article-indicator"></div>
|
||||
<%= image_tag("twitter-logo.svg", class: "icon-img", style: "display:none", alt: "twitter logo") %>
|
||||
<%= image_tag("github-logo.svg", class: "icon-img", style: "display:none", alt: "github logo") %>
|
||||
<% end %>
|
||||
</body>
|
||||
</html>
|
||||
<% end %>
|
||||
<div id="page-content-inner">
|
||||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render "shell/bottom" %>
|
||||
|
|
@ -1,144 +1,136 @@
|
|||
var CACHE_VERSION = 'v2.7.1';
|
||||
var CACHE_NAME = CACHE_VERSION + ':sw-cache::';
|
||||
var REQUESTS_LIMIT = 40;
|
||||
// Serviceworkers file. This code gets installed in users browsers and runs code before the request is made.
|
||||
<% unless Rails.env.test? %>
|
||||
const staticCacheName = 'static-1';
|
||||
const expectedCaches = [
|
||||
staticCacheName
|
||||
];
|
||||
|
||||
function onInstall(event) {
|
||||
event.waitUntil(
|
||||
caches.open(CACHE_NAME).then(function prefill(cache) {
|
||||
return cache.addAll([
|
||||
'<%= asset_path "base.js" %>',
|
||||
'<%= asset_path "minimal.css" %>',
|
||||
'/offline.html',
|
||||
'<%= asset_path "devword.png" %>',
|
||||
'<%= asset_path "wires.png" %>',
|
||||
'<%= asset_path "comments-bubble.png" %>',
|
||||
'<%= asset_path "reactions-stack.png" %>',
|
||||
'<%= asset_path "emoji/emoji-one-heart.png" %>',
|
||||
'<%= asset_path "emoji/emoji-one-unicorn.png" %>',
|
||||
'<%= asset_path "emoji/emoji-one-bookmark.png" %>',
|
||||
'<%= asset_path "emoji/apple-fire.png" %>',
|
||||
]).then(function () {
|
||||
console.log("WORKER: Install completed");
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
self.addEventListener('install', event => {
|
||||
self.skipWaiting();
|
||||
|
||||
function onActivate(event) {
|
||||
console.log('[Serviceworker]', "Activating!", event);
|
||||
event.waitUntil(
|
||||
caches.keys().then(function(cacheNames) {
|
||||
return Promise.all(
|
||||
cacheNames.filter(function(cacheName) {
|
||||
// Return true if you want to remove this cache,
|
||||
return cacheName.indexOf(CACHE_NAME) !== 0;
|
||||
}).map(function(cacheName) {
|
||||
return caches.delete(cacheName);
|
||||
// Populate initial serviceworker cache.
|
||||
event.waitUntil(
|
||||
caches.open(staticCacheName)
|
||||
.then(cache => cache.addAll([
|
||||
"/shell_top", // head, top bar, inline styles
|
||||
"/shell_bottom", // footer
|
||||
"/async_info/shell_version", // For comparing changes in the shell. Should be incremented with style changes.
|
||||
"/404.html", // Not found page
|
||||
"/500.html", // Error page
|
||||
"/offline.html" //Offline page
|
||||
]))
|
||||
);
|
||||
});
|
||||
|
||||
// remove caches that aren't in expectedCaches
|
||||
self.addEventListener('activate', event => {
|
||||
event.waitUntil(
|
||||
caches.keys().then(keys => Promise.all(
|
||||
keys.map(key => {
|
||||
if (!expectedCaches.includes(key)) return caches.delete(key);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
))
|
||||
);
|
||||
});
|
||||
|
||||
function onFetch(event) {
|
||||
if ( requestBlackListed(event.request) ) { return; }
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(function (cached) {
|
||||
if (cached && shouldReturnStraightFromCache(event.request.url)) {
|
||||
return cached;
|
||||
}
|
||||
return fetch(event.request)
|
||||
.then(fetchedFromNetwork)
|
||||
.catch(function fallback(err) {
|
||||
console.log(err)
|
||||
return cached || caches.match('/offline.html');
|
||||
})
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
function requestBlackListed(request){
|
||||
var url = new URL(request.url);
|
||||
var test = (event.request.method !== 'GET' ||
|
||||
event.request.url.match(/\/notifications(\?|\/)/) ||
|
||||
url.host.indexOf("facebook") > -1 ||
|
||||
url.host.indexOf("google") > -1 )
|
||||
return test
|
||||
}
|
||||
|
||||
function shouldReturnStraightFromCache(url) {
|
||||
return (url === "<%= asset_path "base.js" %>" ||
|
||||
url === "<%= asset_path "minimal.css" %>" ||
|
||||
url.indexOf(".self-") > -1 ||
|
||||
(url.indexOf("search?") > -1 && url.indexOf("&i=i") > -1) ||
|
||||
url.indexOf("freetls.fastly.net") > -1)
|
||||
}
|
||||
|
||||
function fetchedFromNetwork(response) {
|
||||
var cacheCopy = response.clone();
|
||||
caches
|
||||
.open(CACHE_NAME + 'pages')
|
||||
.then(function add(cache) {
|
||||
if ( shouldReturnStraightFromCache(event.request.url) ) {
|
||||
cache.put(event.request, cacheCopy);
|
||||
// Create a composed streamed webpage with shell and core content
|
||||
function createPageStream(request) {
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
if (!caches.match('/shell_top') || !caches.match('/shell_top')) { //return if shell isn't cached.
|
||||
return
|
||||
}
|
||||
garbageCollect(cache);
|
||||
})
|
||||
return response;
|
||||
}
|
||||
|
||||
function garbageCollect(cache) {
|
||||
cache.keys().then(function count(keys) {
|
||||
if (keys.length > REQUESTS_LIMIT) {
|
||||
cache.delete(keys[0]);
|
||||
cache.delete(keys[1]);
|
||||
cache.delete(keys[2]);
|
||||
cache.delete(keys[3]);
|
||||
cache.delete(keys[4]);
|
||||
cache.delete(keys[5]);
|
||||
cache.delete(keys[6]);
|
||||
cache.delete(keys[7]);
|
||||
cache.delete(keys[8]);
|
||||
cache.delete(keys[9]);
|
||||
cache.delete(keys[10]);
|
||||
// the body url is the request url plus 'include'
|
||||
const url = new URL(request.url);
|
||||
url.searchParams.set('i', 'i'); // Adds ?i=i or &i=i, which is our indicator for "internal" partial page
|
||||
const startFetch = caches.match('/shell_top');
|
||||
const endFetch = caches.match('/shell_bottom');
|
||||
const middleFetch = fetch(url).then(response => {
|
||||
if (!response.ok && response.status === 404) {
|
||||
return caches.match('/404.html');
|
||||
}
|
||||
if (!response.ok && response.status != 404) {
|
||||
return caches.match('/500.html');
|
||||
}
|
||||
return response;
|
||||
}).catch(err => caches.match('/offline.html'));
|
||||
|
||||
function pushStream(stream) {
|
||||
const reader = stream.getReader();
|
||||
return reader.read().then(function process(result) {
|
||||
if (result.done) return;
|
||||
controller.enqueue(result.value);
|
||||
return reader.read().then(process);
|
||||
});
|
||||
}
|
||||
startFetch
|
||||
.then(response => pushStream(response.body))
|
||||
.then(() => middleFetch)
|
||||
.then(response => pushStream(response.body))
|
||||
.then(() => endFetch)
|
||||
.then(response => pushStream(response.body))
|
||||
.then(() => controller.close());
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {'Content-Type': 'text/html; charset=utf-8'}
|
||||
});
|
||||
}
|
||||
|
||||
function unableToResolve () {
|
||||
console.log('WORKER: fetch request failed in both cache and network.');
|
||||
return caches.match('/offline.html')
|
||||
}
|
||||
};
|
||||
self.addEventListener('fetch', event => {
|
||||
const url = new URL(event.request.url);
|
||||
if (url.origin === location.origin) {
|
||||
//
|
||||
if (event.clientId === "" && // Not fetched via AJAX after page load.
|
||||
event.request.method == "GET" && // Don't fetch on POST, DELETE, etc.
|
||||
!event.request.referrer.includes('/signout_confirm') && // If this is the referrer, we instead want to flush.
|
||||
!url.href.includes('i=i') && // Parameter representing "internal" navigation.
|
||||
!url.href.includes('?signin') && // Don't run on sign in.
|
||||
!url.href.includes('.') && // Don't run on sign in.
|
||||
!url.href.includes('/onboarding') && // Don't run on onboarding.
|
||||
!url.href.includes('/api/') && // Don't run on API endpoints.
|
||||
!url.href.includes('/users/auth') && // Don't run on authentication.
|
||||
!url.href.includes('/shell_') && // Don't fetch for shell.
|
||||
!url.href.includes('/admin') && // Don't fetch for administrate dashboard.
|
||||
!url.href.includes('/internal') && // Don't fetch for internal dashboard.
|
||||
caches.match('/shell_top') && // Ensure shell_top is in the cache.
|
||||
caches.match('/shell_bottom')) { // Ensure shell_bottom is in the cache.
|
||||
event.respondWith(createPageStream(event.request)); // Respond with the stream
|
||||
|
||||
function onPush(event) {
|
||||
var title = 'DEV Connect 👋 ';
|
||||
var body = event.data.text();
|
||||
var icon = '<%= image_path "devlogo-pwa-128.png" %>';
|
||||
var tag = 'default-tag' + body;
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(title, {
|
||||
body: body,
|
||||
icon: icon,
|
||||
tag: tag,
|
||||
data: {
|
||||
url: 'https://dev.to/connect'
|
||||
// Ping version endpoint to see if we should fetch new shell.
|
||||
if (!caches.match('/async_info/shell_version')) { // Check if we have a cached shell version
|
||||
caches.open(staticCacheName)
|
||||
.then(cache => cache.addAll([
|
||||
"/async_info/shell_version",
|
||||
]))
|
||||
return
|
||||
}
|
||||
fetch('/async_info/shell_version').then(response => response.json()).then(json => {
|
||||
caches.match('/async_info/shell_version').then(cachedResponse => cachedResponse.json()).then(cacheJson => {
|
||||
if (cacheJson['version'] != json['version']) {
|
||||
caches.open(staticCacheName)
|
||||
.then(cache => cache.addAll([
|
||||
"/shell_top",
|
||||
"/shell_bottom",
|
||||
"/async_info/shell_version"
|
||||
]))
|
||||
}
|
||||
})
|
||||
});
|
||||
return;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
function onNotificationClick(event) {
|
||||
if (Notification.prototype.hasOwnProperty('data')) {
|
||||
var url = event.notification.data.url;
|
||||
event.waitUntil(clients.openWindow(url));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.addEventListener('install', onInstall);
|
||||
self.addEventListener('activate', onActivate);
|
||||
self.addEventListener('fetch', onFetch);
|
||||
self.addEventListener('push', onPush);
|
||||
self.addEventListener('notificationclick', onNotificationClick)
|
||||
// Fetch new shell upon events that signify change in session.
|
||||
if (event.clientId === "" &&
|
||||
(event.request.referrer.includes('/signout_confirm') || url.href.includes('?signin') || url.href.includes('/onboarding'))) {
|
||||
caches.open(staticCacheName)
|
||||
.then(cache => cache.addAll([
|
||||
"/shell_top",
|
||||
"/shell_bottom",
|
||||
]))
|
||||
}
|
||||
}
|
||||
});
|
||||
<% end %>
|
||||
19
app/views/shell/_bottom.html.erb
Normal file
19
app/views/shell/_bottom.html.erb
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<!-- Start Bottom Shell -->
|
||||
<% unless internal_navigation? %>
|
||||
<% cache("footer-and-signup-modal--#{user_signed_in?}--#{ApplicationConfig['HEROKU_SLUG_COMMIT']}", expires_in: 24.hours) do %>
|
||||
<%= render "layouts/footer" %>
|
||||
<%= render "layouts/signup_modal" unless user_signed_in? %>
|
||||
<% end %>
|
||||
<div id="live-article-indicator" class="live-article-indicator"></div>
|
||||
<%= image_tag("twitter-logo.svg", class: "icon-img", style: "display:none", alt: "twitter logo") %>
|
||||
<%= image_tag("github-logo.svg", class: "icon-img", style: "display:none", alt: "github logo") %>
|
||||
<% if @shell %>
|
||||
<script defer>
|
||||
if (!document.title) {
|
||||
document.title = document.getElementsByTagName("TITLE")[1].innerHTML
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
</body>
|
||||
</html>
|
||||
<% end %>
|
||||
75
app/views/shell/_top.html.erb
Normal file
75
app/views/shell/_top.html.erb
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<% title = yield(:title) %>
|
||||
<title><%= title || "#{ApplicationConfig['COMMUNITY_NAME']} Community" %></title>
|
||||
<% unless internal_navigation? %>
|
||||
<meta name="last-updated" content="<%= Time.current %>">
|
||||
<meta name="user-signed-in" content="<%= user_signed_in? %>">
|
||||
<meta name="algolia-public-id" content="<%= ApplicationConfig["ALGOLIASEARCH_APPLICATION_ID"] %>">
|
||||
<meta name="algolia-public-key" content="<%= ALGOLIASEARCH_PUBLIC_SEARCH_ONLY_KEY %>">
|
||||
<meta name="environment" content="<%= Rails.env %>">
|
||||
<%= render "layouts/styles" %>
|
||||
<style>
|
||||
.home {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
min-height: 440px;
|
||||
margin: auto;
|
||||
max-width: 1250px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 950px) {
|
||||
.home {
|
||||
margin-top: 26px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<%= javascript_pack_tag "manifest", defer: true %>
|
||||
<%= javascript_pack_tag "vendor", defer: true %>
|
||||
<%= javascript_pack_tag "Search", defer: true %>
|
||||
<%= javascript_include_tag "base", defer: true %>
|
||||
<% if user_signed_in? %>
|
||||
<%= javascript_pack_tag "onboardingRedirectCheck", defer: true %>
|
||||
<%= javascript_pack_tag "contentDisplayPolicy", defer: true %>
|
||||
<% end %>
|
||||
<%= yield(:page_meta) %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<%= favicon_link_tag ApplicationConfig["FAVICON_URL"] %>
|
||||
<link rel="apple-touch-icon" href="<%= asset_path "apple-icon.png" %>">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= asset_path "apple-icon-152x152.png" %>">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= asset_path "apple-icon-180x180.png" %>">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= asset_path "apple-icon-167x167.png" %>">
|
||||
<link href="<%= asset_path "android-icon-192x192.png" %>" rel="icon" sizes="192x192" />
|
||||
<link href="<%= asset_path "android-icon-128x128.png" %>" rel="icon" sizes="128x128" />
|
||||
<meta name="apple-mobile-web-app-title" content="dev.to">
|
||||
<meta name="application-name" content="dev.to">
|
||||
<meta property="fb:pages" content="568966383279687" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<% if view_class.include? "users-signout_confirm" %>
|
||||
<%= csrf_meta_tags %>
|
||||
<% end %>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="search" href="https://dev.to/search.xml" type="application/opensearchdescription+xml" title="<%= community_qualified_name %>" />
|
||||
<% end %>
|
||||
</head>
|
||||
<% unless internal_navigation? %>
|
||||
<body data-user-status="<%= user_logged_in_status %>" data-pusher-key="<%= ApplicationConfig["PUSHER_KEY"] %>">
|
||||
<div id="body-styles"></div>
|
||||
<% if user_signed_in? %>
|
||||
<%= render "layouts/user_config" %>
|
||||
<% end %>
|
||||
<div id="audiocontent">
|
||||
<%= yield(:audio) %>
|
||||
</div>
|
||||
<% cache("application-top-bar--#{user_signed_in?}", expires_in: 100.hours) do %>
|
||||
<%= render "layouts/top_bar" %>
|
||||
<% end %>
|
||||
<div id="message-notice"></div>
|
||||
<div class="app-shell-loader">
|
||||
loading...
|
||||
</div>
|
||||
<!-- End Top Shell -->
|
||||
<% end %>
|
||||
|
|
@ -1,9 +1,9 @@
|
|||
<% title "Confirm Signout - DEV" %>
|
||||
|
||||
<div class="signout_confirm-wrapper">
|
||||
|
||||
<h1>Are you sure you want to sign out?</h1>
|
||||
|
||||
<h2><%= link_to "Yes. Sign out.", destroy_user_session_path, method: :delete, data: { "no-instant" => "" } %></h2>
|
||||
<%= form_tag destroy_user_session_path, method: :delete do %>
|
||||
<button class="cta">Yes. Sign out.</button>
|
||||
<% end %>
|
||||
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -102,8 +102,8 @@ Rails.application.configure do
|
|||
config.logger = ActiveSupport::TaggedLogging.new(logger)
|
||||
|
||||
config.after_initialize do
|
||||
Bullet.enable = true
|
||||
Bullet.console = true
|
||||
Bullet.enable = false
|
||||
Bullet.console = false
|
||||
Bullet.rails_logger = true
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ Rails.application.configure do
|
|||
# Apache or NGINX already handles this.
|
||||
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present?
|
||||
config.public_file_server.headers = {
|
||||
"Cache-Control" => "public, s-maxage=2592000, max-age=86400"
|
||||
"Cache-Control" => "public, s-maxage=#{30.days.to_i}, max-age=#{30.days.to_i}"
|
||||
}
|
||||
|
||||
# Compress JavaScripts and CSS.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
FastlyRails.configure do |c|
|
||||
c.api_key = ApplicationConfig["FASTLY_API_KEY"] # Fastly api key, required
|
||||
c.max_age = 30.days.to_i # time in seconds, optional, defaults to 2592000 (30 days)
|
||||
c.max_age = 1.day.to_i # time in seconds, optional, defaults to 2592000 (30 days)
|
||||
c.service_id = ApplicationConfig["FASTLY_SERVICE_ID"] # Fastly service you will be using, required
|
||||
c.stale_if_error = 26_400
|
||||
c.purging_enabled = Rails.env.production? # No need to configure a client locally (since 0.4.0)
|
||||
|
|
|
|||
4
config/initializers/initialization_cachebust.rb
Normal file
4
config/initializers/initialization_cachebust.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Trigger cache purges for globally-cached endpoints that could have changed
|
||||
CacheBuster.bust("/shell_top")
|
||||
CacheBuster.bust("/shell_bottom")
|
||||
CacheBuster.bust("/async_info/shell_version")
|
||||
|
|
@ -235,7 +235,8 @@ Rails.application.routes.draw do
|
|||
get "/social_previews/comment/:id" => "social_previews#comment", :as => :comment_social_preview
|
||||
|
||||
get "/async_info/base_data", controller: "async_info#base_data", defaults: { format: :json }
|
||||
|
||||
get "/async_info/shell_version", controller: "async_info#shell_version", defaults: { format: :json }
|
||||
|
||||
get "/future", to: redirect("devteam/the-future-of-dev-160n")
|
||||
|
||||
# Settings
|
||||
|
|
@ -338,6 +339,10 @@ Rails.application.routes.draw do
|
|||
get "/serviceworker" => "service_worker#index"
|
||||
get "/manifest" => "service_worker#manifest"
|
||||
|
||||
get "/shell_top" => "shell#top"
|
||||
get "/shell_bottom" => "shell#bottom"
|
||||
|
||||
|
||||
get "/new" => "articles#new"
|
||||
get "/new/:template" => "articles#new"
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,20 @@ rule, you should avoid relying on JavaScript for layout when working on DEV.
|
|||
We use [PreactJS][preact], a lightweight alternative to ReactJS, and we try to
|
||||
reduce our bundle size with [dynamic imports][dynamic_imports].
|
||||
|
||||
## Service workers and shell architecture
|
||||
|
||||
We make use of serviceworkers to cache portions of the page.
|
||||
|
||||
We cache styles here as well as script fingerprinting, so we should increment
|
||||
the number in `/async_info/shell_version` any time we change core CSS or
|
||||
JavaScript.
|
||||
|
||||
Serviceworkers can be controlled in the `application` tab of Chrome.
|
||||
Serviceworkers are a reverse proxy that runs in the browser in a non-blocking
|
||||
thread, supported by most major browsers. You may want to disable or bypass
|
||||
Serviceworkers in development while making changes to avoid having everything
|
||||
cached.
|
||||
|
||||
## Worst technical debt
|
||||
|
||||
The most widespread elements of technical debt in this application reside on the
|
||||
|
|
@ -63,16 +77,16 @@ shared among all users.
|
|||
|
||||
## Inter-page navigation
|
||||
|
||||
DEV uses a variation of "instant click," which swaps out page content instead
|
||||
of making full-page requests. This approach is similar to the one used by the
|
||||
Rails gem `Turbolinks`, but our approach is more lightweight. The library is
|
||||
modified to work specifically with this Rails app and does not swap out reused
|
||||
elements like the navigation bar or the footer. The code for this functionality
|
||||
is viewable in `app/assets/javascripb/base.js.erb`.
|
||||
DEV uses a variation of "instant click," which swaps out page content instead of
|
||||
making full-page requests. This approach is similar to the one used by the Rails
|
||||
gem `Turbolinks`, but our approach is more lightweight. The library is modified
|
||||
to work specifically with this Rails app and does not swap out reused elements
|
||||
like the navigation bar or the footer. The code for this functionality is
|
||||
viewable in `app/assets/javascripb/base.js.erb`.
|
||||
|
||||
There are a few caveats regarding this approach. Using our approach means a
|
||||
non-trivial amount of functionality is reloaded on page change. A similar
|
||||
amount of reloading occurs when using `window.InstantClick.on('change', someFunction)`.
|
||||
non-trivial amount of functionality is reloaded on page change. A similar amount
|
||||
of reloading occurs when using `window.InstantClick.on('change', someFunction)`.
|
||||
This results in code that looks something like this:
|
||||
|
||||
```javascript
|
||||
|
|
@ -92,9 +106,9 @@ that front is welcome!
|
|||
## Articles (or posts)
|
||||
|
||||
Articles are the primary form of user generated content in the application. An
|
||||
Article has many comments and taggings through the acts-as-taggable gem,
|
||||
belongs to a single user (and possibly an organization), and is the core unit
|
||||
of content.
|
||||
Article has many comments and taggings through the acts-as-taggable gem, belongs
|
||||
to a single user (and possibly an organization), and is the core unit of
|
||||
content.
|
||||
|
||||
## Comments
|
||||
|
||||
|
|
@ -149,5 +163,6 @@ This is far from a complete view of the app, but it covers a few core concepts.
|
|||
[fastly]: https://www.fastly.com/
|
||||
[rails_caching]: https://guides.rubyonrails.org/caching_with_rails.html
|
||||
[preact]: https://preactjs.com/
|
||||
[dynamic_imports]: https://dev.to/goenning/how-we-reduced-our-initial-jscss-size-by-67-3ac0
|
||||
[dynamic_imports]:
|
||||
https://dev.to/goenning/how-we-reduced-our-initial-jscss-size-by-67-3ac0
|
||||
[fastly_rails]: https://github.com/fastly/fastly-rails
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
<title>The page you were looking for doesn't exist (404)</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
.not-found-page {
|
||||
height: 100vh;
|
||||
min-height: 500px;
|
||||
}
|
||||
.tv__outer {
|
||||
display: table;
|
||||
position: absolute;
|
||||
|
|
@ -41,15 +45,18 @@
|
|||
margin-right: auto;
|
||||
display: table;
|
||||
font-size: 22px;
|
||||
font-weight: 200;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 85%;
|
||||
width: 300px;
|
||||
border: 25px solid rgb(54, 55, 124);
|
||||
border-radius: 36px;
|
||||
}
|
||||
p {
|
||||
text-align: center;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
@-webkit-keyframes fade {
|
||||
|
|
@ -81,13 +88,15 @@
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<div class="tv__outer">
|
||||
<div class="tv__middle">
|
||||
<img class="tv__inner--special" src="https://i.imgur.com/Vyyjycv.gif" alt="404 not found image as TV static">
|
||||
<img class="tv__inner" src="https://i.imgur.com/3v0LCiP.jpg" alt="404 not found DEV logo">
|
||||
<br>
|
||||
<p class="tv__inner--text">You have lost your way. <br> <a href="https://dev.to/">Return to Home Page</a></p>
|
||||
</div>
|
||||
<div class="not-found-page base-background-color">
|
||||
<div class="tv__outer">
|
||||
<div class="tv__middle">
|
||||
<img class="tv__inner--special" src="https://i.imgur.com/Vyyjycv.gif" alt="404 not found image as TV static">
|
||||
<img class="tv__inner" src="https://i.imgur.com/3v0LCiP.jpg" alt="404 not found DEV logo">
|
||||
<br>
|
||||
<p class="tv__inner--text">This page does not exist <br><br> <a href="https://dev.to/">Return to Home Page</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -4,22 +4,26 @@
|
|||
<title>We're sorry, but something went wrong (500)</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
body {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
.unhandled-error-page {
|
||||
height: 100vh;
|
||||
min-height: 550px;
|
||||
padding-top: 10vh;
|
||||
}
|
||||
.dialog{
|
||||
background:rgb(236, 236, 236);
|
||||
border:1px solid rgb(217, 217, 217);
|
||||
border:2px solid rgb(217, 217, 217);
|
||||
text-align:center;
|
||||
padding:20px 0px;
|
||||
padding:30px 0px;
|
||||
margin:5% auto;
|
||||
width:80%;
|
||||
max-width:600px;
|
||||
max-width:580px;
|
||||
min-width:315px;
|
||||
color:rgb(44, 46, 50);
|
||||
border-radius:5px;
|
||||
min-height: 600px;
|
||||
border-radius:8px;
|
||||
min-height: 500px;
|
||||
}
|
||||
textarea{
|
||||
width:75%;
|
||||
|
|
@ -51,6 +55,7 @@
|
|||
img{
|
||||
max-width:100%;
|
||||
height:200px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
<script src='https://www.google.com/recaptcha/api.js'></script>
|
||||
|
|
@ -58,13 +63,15 @@
|
|||
|
||||
<body>
|
||||
<!-- This file lives in public/500.html -->
|
||||
<div class="dialog">
|
||||
<div>
|
||||
<img alt="500 error" src="https://media.giphy.com/media/SX9TFmjF0eChq/200.gif" height="200" />
|
||||
<h1>500 Error</h1>
|
||||
<h2>It's not you, it's me.</h2>
|
||||
<div class="unhandled-error-page base-background-color">
|
||||
<div class="dialog">
|
||||
<div>
|
||||
<img alt="500 error" src="https://media.giphy.com/media/SX9TFmjF0eChq/200.gif" height="200" />
|
||||
<h1>Ooooops...</h1>
|
||||
<h2>You have encountered an error.</h2>
|
||||
</div>
|
||||
<p style="margin: 10%">Our team has been notified. Please feel welcome to also submit a public <a href="https://github.com/thepracticaldev/dev.to/issues/new?template=bug_report.md">bug report</a>. <br><br/>We apologize for any inconvenience.</p>
|
||||
</div>
|
||||
<p style="margin: 10%">Sorry you're hitting this page. <br> Please help us by submitting a <a href="https://github.com/thepracticaldev/dev.to/issues/new?template=bug_report.md">bug report</a>. <br>Thank you!</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@
|
|||
|
||||
.rainbow-logo {
|
||||
border-radius: 10%;
|
||||
max-width: 50%;
|
||||
max-width: 40%;
|
||||
max-height: 400px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@ RSpec.describe "AsyncInfo", type: :request do
|
|||
end
|
||||
end
|
||||
|
||||
describe "GET /async_info/shell_version" do
|
||||
it "returns shell_version" do
|
||||
get "/async_info/shell_version"
|
||||
expect(response.body).to include("version")
|
||||
end
|
||||
end
|
||||
|
||||
describe "#remember_user_token" do
|
||||
# We require the remember_user_token key bc we also use it for caching in Fastly
|
||||
# If this key changes, Fastly needs to be updated
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ RSpec.describe "ServiceWorker", type: :request do
|
|||
describe "GET /serviceworker.js" do
|
||||
it "renders file with proper text" do
|
||||
get "/serviceworker.js"
|
||||
expect(response.body).to include("var CACHE_VERSION")
|
||||
expect(response.body).to include("Serviceworkers file")
|
||||
end
|
||||
|
||||
it "renders javascript file" do
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ RSpec.describe "Deleting Comment", type: :system, js: true do
|
|||
it "works" do
|
||||
visit "/"
|
||||
visit comment.path + "/delete_confirm"
|
||||
click_link("DELETE")
|
||||
click_button("DELETE")
|
||||
expect(page).to have_current_path(article.path)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue