This reverts commit 2416387fb4.
This commit is contained in:
parent
4e997e7e3a
commit
7c1343be9e
24 changed files with 125 additions and 795 deletions
|
|
@ -1,5 +1,4 @@
|
|||
# Development options
|
||||
SKIP_SERVICEWORKERS="true"
|
||||
|
||||
# App core values
|
||||
APP_DOMAIN="localhost:3000"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
//= require utilities/getImageForLink
|
||||
//= require honeybadger-js/dist/honeybadger.js
|
||||
//= require ahoy.js/dist/ahoy.js
|
||||
//= require serviceworker-companion
|
||||
|
||||
var instantClick
|
||||
, InstantClick = instantClick = function(document, location, $userAgent) {
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
if ('serviceWorker' in navigator) {
|
||||
// Safari has issues with the service worker, so we'll just skip it on those browsers, and unregister it if it's loaded
|
||||
if (
|
||||
/Safari/i.test(navigator.userAgent) &&
|
||||
/Apple Computer/.test(navigator.vendor)
|
||||
) {
|
||||
//unregister serviceworker
|
||||
navigator.serviceWorker
|
||||
.getRegistration('/serviceworker.js')
|
||||
.then(function (registration) {
|
||||
if (registration) {
|
||||
registration.unregister();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
navigator.serviceWorker
|
||||
.register('/serviceworker.js', { scope: '/' })
|
||||
.then(function swStart(registration) {
|
||||
// registered!
|
||||
})
|
||||
.catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('ServiceWorker registration failed: ', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('beforeinstallprompt', (e) => {
|
||||
// beforeinstallprompt Event fired
|
||||
// e.userChoice will return a Promise.
|
||||
e.userChoice.then((choiceResult) => {
|
||||
ga('send', 'event', 'PWA-install', choiceResult.outcome);
|
||||
});
|
||||
});
|
||||
|
|
@ -41,14 +41,8 @@ class ApplicationController < ActionController::Base
|
|||
health_checks].freeze
|
||||
private_constant :PUBLIC_CONTROLLERS
|
||||
|
||||
# TODO: Remove the "shell" endpoints, because they are for service worker
|
||||
# functionality we no longer need. We are keeping these around mid-March
|
||||
# 2021 because previously-installed service workers may still expect them.
|
||||
CONTENT_CHANGE_PATHS = [
|
||||
"/tags/onboarding", # Needs to change when suggested_tags is edited.
|
||||
"/shell_top", # Cached at edge, sent to service worker.
|
||||
"/shell_bottom", # Cached at edge, sent to service worker.
|
||||
"/async_info/shell_version", # Checks if current users should be busted.
|
||||
"/onboarding", # Page is cached at edge.
|
||||
"/", # Page is cached at edge.
|
||||
].freeze
|
||||
|
|
|
|||
|
|
@ -83,8 +83,7 @@ class PagesController < ApplicationController
|
|||
end
|
||||
|
||||
def report_abuse
|
||||
referer = URL.sanitized_referer(request.referer)
|
||||
reported_url = params[:reported_url] || params[:url] || referer
|
||||
reported_url = params[:reported_url] || params[:url] || request.referer.presence
|
||||
@feedback_message = FeedbackMessage.new(
|
||||
reported_url: reported_url&.chomp("?i=i"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,8 +5,7 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
if user_signed_in?
|
||||
redirect_to root_path(signin: "true")
|
||||
else
|
||||
referer_path = URI(request.referer || "").path
|
||||
if URI(request.referer || "").host == URI(request.base_url).host && referer_path != "/serviceworker.js"
|
||||
if URI(request.referer || "").host == URI(request.base_url).host
|
||||
store_location_for(:user, request.referer)
|
||||
end
|
||||
super
|
||||
|
|
|
|||
|
|
@ -1,14 +1,9 @@
|
|||
class ServiceWorkerController < ApplicationController
|
||||
skip_before_action :verify_authenticity_token
|
||||
before_action :set_cache_control_headers, only: %i[index manifest]
|
||||
before_action :set_cache_control_headers, only: %i[index]
|
||||
|
||||
def index
|
||||
set_surrogate_key_header "serviceworker-js"
|
||||
render formats: [:js]
|
||||
end
|
||||
|
||||
def manifest
|
||||
set_surrogate_key_header "manifest-json"
|
||||
render formats: [:json]
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -261,10 +261,6 @@ module ApplicationHelper
|
|||
URL.organization(organization)
|
||||
end
|
||||
|
||||
def sanitized_referer(referer)
|
||||
URL.sanitized_referer(referer)
|
||||
end
|
||||
|
||||
def sanitize_and_decode(str)
|
||||
# using to_str instead of to_s to prevent removal of html entity code
|
||||
HTMLEntities.new.decode(sanitize(str).to_str)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
# Utilities methods to safely build app wide URLs
|
||||
module URL
|
||||
SERVICE_WORKER = "/serviceworker.js".freeze
|
||||
|
||||
def self.protocol
|
||||
ApplicationConfig["APP_PROTOCOL"]
|
||||
end
|
||||
|
|
@ -72,19 +70,4 @@ module URL
|
|||
def self.organization(organization)
|
||||
url(organization.slug)
|
||||
end
|
||||
|
||||
# Ensures we don't consider serviceworker.js as referer
|
||||
#
|
||||
# @param referer [String] the unsanitized referer
|
||||
# @example A safe referer
|
||||
# sanitized_referer("/some/path") #=> "/some/path"
|
||||
# @example serviceworker.js as referer
|
||||
# sanitized_referer("serviceworker.js") #=> nil
|
||||
# @example An empty string
|
||||
# sanitized_referer("") #=> nil
|
||||
def self.sanitized_referer(referer)
|
||||
return if referer.blank? || URI(referer).path == SERVICE_WORKER
|
||||
|
||||
referer
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -48,7 +48,8 @@
|
|||
<% end %>
|
||||
|
||||
<% if internal_navigation? %>
|
||||
<!-- If you have service workers turned on, you may be seeing the canonical URL in the body for human inspection -->
|
||||
<!-- You may be seeing the canonical URL in the body for human inspection -->
|
||||
<!-- This is because of navigation via InstantClick, instantclick.io -->
|
||||
<!-- However, the crawled version of the page has the canonical in the head. -->
|
||||
<!-- This is confirmed by the canonical url inspector. See https://github.com/forem/forem/issues/9509#issuecomment-732259221. -->
|
||||
<link rel="canonical" href="<%= @article.canonical_url.presence || app_url(@article.path) %>" />
|
||||
|
|
|
|||
|
|
@ -1,4 +1,86 @@
|
|||
<%= render "shell/top" %>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<% title = yield(:title) %>
|
||||
<title><%= title || community_name.to_s %></title>
|
||||
<% unless internal_navigation? %>
|
||||
<meta name="last-updated" content="<%= Time.current %>">
|
||||
<meta name="user-signed-in" content="<%= user_signed_in? %>">
|
||||
<meta name="head-cached-at" content="<%= Time.current.to_i %>">
|
||||
<% if SiteConfig.payment_pointer.present? %>
|
||||
<!-- Experimental web monetization payment pointer for micropayments -->
|
||||
<!-- It lets readers make micropayments to websites they visit. -->
|
||||
<!-- This is step 1: Get live in production to test for platform-wide payment pointer. -->
|
||||
<!-- Step 2: Allow authors to set their payment pointer so they can directly monetize their content based on visitors. -->
|
||||
<!-- Step 3: Enable further functionality based on what we learn from this experimentation and how the ecosystem evolves. -->
|
||||
<meta name="monetization" content="<%= SiteConfig.payment_pointer %>">
|
||||
<% end %>
|
||||
<meta name="environment" content="<%= Rails.env %>">
|
||||
<%= render "layouts/styles", qualifier: "main" %>
|
||||
<% unless user_signed_in? %>
|
||||
<%= javascript_packs_with_chunks_tag "base", "Search", defer: true %>
|
||||
<% end %>
|
||||
<%= javascript_include_tag "base", defer: true %>
|
||||
<% if user_signed_in? %>
|
||||
<%= javascript_packs_with_chunks_tag "base", "Search", "onboardingRedirectCheck", "contentDisplayPolicy", defer: true %>
|
||||
<% end %>
|
||||
<%= yield(:page_meta) %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<%= favicon_link_tag Images::Optimizer.call(SiteConfig.favicon_url, width: 32) %>
|
||||
<link rel="apple-touch-icon" href="<%= optimized_image_url(SiteConfig.logo_png, width: 180, fetch_format: "png") %>">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= optimized_image_url(SiteConfig.logo_png, width: 152, fetch_format: "png") %>">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= optimized_image_url(SiteConfig.logo_png, width: 180, fetch_format: "png") %>">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= optimized_image_url(SiteConfig.logo_png, width: 167, fetch_format: "png") %>">
|
||||
<link href="<%= optimized_image_url(SiteConfig.logo_png, width: 192, fetch_format: "png") %>" rel="icon" sizes="192x192" />
|
||||
<link href="<%= optimized_image_url(SiteConfig.logo_png, width: 128, fetch_format: "png") %>" rel="icon" sizes="128x128" />
|
||||
<meta name="apple-mobile-web-app-title" content="<%= SiteConfig.app_domain %>">
|
||||
<meta name="application-name" content="<%= SiteConfig.app_domain %>">
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="search" href="<%= URL.url("open-search.xml") %>" type="application/opensearchdescription+xml" title="<%= community_name %>" />
|
||||
|
||||
<meta property="forem:name" content="<%= community_name %>" />
|
||||
<meta property="forem:logo" content="<%= optimized_image_url(SiteConfig.logo_png, width: 512, fetch_format: "png") %>" />
|
||||
<meta property="forem:domain" content="<%= SiteConfig.app_domain %>" />
|
||||
<% end %>
|
||||
</head>
|
||||
<% unless internal_navigation? %>
|
||||
<% cache(release_adjusted_cache_key("top-html-and-config--#{user_signed_in?}")) do %>
|
||||
<body
|
||||
data-user-status="<%= user_logged_in_status %>"
|
||||
class="<%= SiteConfig.default_font.tr("_", "-") %>-article-body default-header"
|
||||
data-pusher-key="<%= ApplicationConfig["PUSHER_KEY"] %>"
|
||||
data-app-domain="<%= ChatChannel.urlsafe_encoded_app_domain %>"
|
||||
data-honeybadger-key="<%= ApplicationConfig["HONEYBADGER_JS_API_KEY"] %>"
|
||||
data-deployed-at="<%= ForemInstance.deployed_at %>"
|
||||
data-latest-commit-id="<%= ForemInstance.latest_commit_id %>"
|
||||
data-ga-tracking="<%= SiteConfig.ga_tracking_id %>">
|
||||
<%# Repeat of stylesheets in <head> to fix rendering glitch: https://github.com/forem/forem/issues/12377 %>
|
||||
<%= render "layouts/styles", qualifier: "secondary" %>
|
||||
<div id="body-styles">
|
||||
<style>
|
||||
:root {
|
||||
--accent-brand: <%= SiteConfig.primary_brand_color_hex %>;
|
||||
--accent-brand-darker: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).brightness(0.85) %>;
|
||||
--accent-brand-lighter: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).brightness(1.1) %>;
|
||||
--accent-brand-a10: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).opacity(0.1) %>;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<% if user_signed_in? %>
|
||||
<%= render "layouts/user_config" %>
|
||||
<% end %>
|
||||
<div id="audiocontent" data-podcast="">
|
||||
<%= yield(:audio) %>
|
||||
</div>
|
||||
<%= render "layouts/top_bar" %>
|
||||
<div id="active-broadcast" class="broadcast-wrapper"></div>
|
||||
<div id="message-notice"></div>
|
||||
<div class="app-shell-loader">
|
||||
loading...
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if SiteConfig.payment_pointer.present? %>
|
||||
<div id="base-payment-pointer" data-payment-pointer="<%= SiteConfig.payment_pointer %>"></div>
|
||||
<% end %>
|
||||
|
|
@ -24,4 +106,18 @@
|
|||
<%= yield %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render "shell/bottom" %>
|
||||
<% unless internal_navigation? %>
|
||||
<% cache("footer-and-signup-modal-#{user_signed_in?}-#{ForemInstance.deployed_at}-#{SiteConfig.admin_action_taken_at&.rfc3339}", expires_in: 24.hours) do %>
|
||||
<%= render "layouts/footer" %>
|
||||
<%= render "layouts/signup_modal" unless user_signed_in? %>
|
||||
<% end %>
|
||||
<% if @shell %>
|
||||
<script defer>
|
||||
if (!document.title) {
|
||||
document.title = document.getElementsByTagName("TITLE")[1].textContent
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
</body>
|
||||
</html>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -1,91 +1,21 @@
|
|||
// Service worker file. Renders as serviceworker.js
|
||||
// This is now a self-destructing service worker. See https://github.com/NekR/self-destroying-sw
|
||||
|
||||
<% unless Rails.env.test? || ENV["SKIP_SERVICEWORKERS"] == "true" %>
|
||||
const VERSION = "1.0" // Increment to invalidate old assets.
|
||||
const OFFLINE_URL = "offline.html";
|
||||
const OFFLINE_NAME = OFFLINE_URL + VERSION
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
|
||||
// Setting {cache: 'reload'} in the new request will ensure that the
|
||||
// response isn't fulfilled from the HTTP cache; i.e., it will be from
|
||||
// the network.
|
||||
const offlineCache = await caches.open(OFFLINE_NAME);
|
||||
await offlineCache.add(new Request(OFFLINE_URL, { cache: "reload" }));
|
||||
})()
|
||||
);
|
||||
// Force the waiting service worker to become the active service worker.
|
||||
<% unless Rails.env.test? %>
|
||||
self.addEventListener('install', function(e) {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
event.waitUntil(
|
||||
(async () => {
|
||||
// Enable navigation preload if it's supported.
|
||||
// See https://developers.google.com/web/updates/2017/02/navigation-preload
|
||||
if ("navigationPreload" in self.registration) {
|
||||
await self.registration.navigationPreload.enable();
|
||||
}
|
||||
})()
|
||||
);
|
||||
|
||||
// Tell the active service worker to take control of the page immediately.
|
||||
self.clients.claim();
|
||||
});
|
||||
|
||||
function includesUnsupportedPath(url) {
|
||||
return [
|
||||
'/%F0%9F%92%B8', // 💸 (hiring)
|
||||
'/admin', // Don't run on admin dashboard.
|
||||
'/new', // Don't run on editor.
|
||||
'/enter', // Don't run on sign in page.
|
||||
'/oauth/', // Skip oauth apps
|
||||
'/onboarding', // Don't run on onboarding.
|
||||
'/shop', // Don't run on Shop
|
||||
'/sidekiq', // Skip for Sidekiq dashboard
|
||||
'/users/auth', // Don't run on authentication.
|
||||
'/users/sign_in', // Don't run on sign in page.
|
||||
'/welcome', // Don't run on welcome reroutes.
|
||||
].some(path => url.includes(path))
|
||||
}
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
// We only want to call event.respondWith() if this is a navigation request
|
||||
// for an HTML page with a few exceptions.
|
||||
if (event.request.mode === "navigate" && !includesUnsupportedPath(event.request.url)) {
|
||||
event.respondWith(
|
||||
(async () => {
|
||||
try {
|
||||
// First, try to use the navigation preload response if it's supported.
|
||||
const preloadResponse = await event.preloadResponse;
|
||||
if (preloadResponse) {
|
||||
return preloadResponse;
|
||||
}
|
||||
|
||||
// Always try the network first.
|
||||
const networkResponse = await fetch(event.request);
|
||||
return networkResponse;
|
||||
} catch (error) {
|
||||
// catch is only triggered if an exception is thrown, which is likely
|
||||
// due to a network error.
|
||||
// If fetch() returns a valid HTTP response with a response code in
|
||||
// the 4xx or 5xx range, the catch() will NOT be called.
|
||||
console.log("Fetch failed; returning offline page instead.", error);
|
||||
|
||||
const cache = await caches.open(OFFLINE_NAME);
|
||||
const cachedResponse = await cache.match(OFFLINE_URL);
|
||||
return cachedResponse;
|
||||
}
|
||||
})()
|
||||
);
|
||||
}
|
||||
|
||||
// If our if() condition is false, then this fetch handler won't intercept the
|
||||
// request. If there are any other fetch handlers registered, they will get a
|
||||
// chance to call event.respondWith(). If no fetch handlers call
|
||||
// event.respondWith(), the request will be handled by the browser as if there
|
||||
// were no service worker involvement.
|
||||
self.addEventListener('activate', function(e) {
|
||||
self.registration.unregister()
|
||||
.then(function() {
|
||||
return self.clients.matchAll();
|
||||
})
|
||||
.then(function(clients) {
|
||||
clients.forEach(client => {
|
||||
// Check if client.navigate is supported before running it.
|
||||
client.navigate && client.navigate(client.url);
|
||||
})
|
||||
});
|
||||
});
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -1,26 +0,0 @@
|
|||
{
|
||||
"name": "<%= community_name %>",
|
||||
"short_name": "<%= SiteConfig.app_domain %>",
|
||||
"description": "<%= SiteConfig.community_description %>",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#000000",
|
||||
"theme_color": "#000000",
|
||||
"homepage_url": "<%= app_url %>",
|
||||
"icons": [{
|
||||
"src": "<%= optimized_image_url(SiteConfig.logo_png, width: 192, fetch_format: "png") %>",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}, {
|
||||
"src": "<%= optimized_image_url(SiteConfig.logo_png, width: 128, fetch_format: "png") %>",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}, {
|
||||
"src": "<%= optimized_image_url(SiteConfig.logo_png, width: 512, fetch_format: "png") %>",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
}]
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
<% # TODO: This file supports removed service worker functionality. We are keeping it around until mid-March to support previously downloaded service workers. %>
|
||||
<% # After that, these contents can be moved right into application.html. %>
|
||||
|
||||
<!-- Start Bottom Shell -->
|
||||
<% unless internal_navigation? %>
|
||||
<% cache("footer-and-signup-modal-#{user_signed_in?}-#{ForemInstance.deployed_at}-#{SiteConfig.admin_action_taken_at&.rfc3339}", expires_in: 24.hours) do %>
|
||||
<%= render "layouts/footer" %>
|
||||
<%= render "layouts/signup_modal" unless user_signed_in? %>
|
||||
<% end %>
|
||||
<% if @shell %>
|
||||
<script defer>
|
||||
if (!document.title) {
|
||||
document.title = document.getElementsByTagName("TITLE")[1].textContent
|
||||
}
|
||||
</script>
|
||||
<% end %>
|
||||
</body>
|
||||
</html>
|
||||
<% end %>
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
<% # TODO: This file supports removed service worker functionality. We are keeping it around until mid-March to support previously downloaded service workers. %>
|
||||
<% # After that, these contents can be moved right into application.html. %>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<% title = yield(:title) %>
|
||||
<title><%= title || community_name.to_s %></title>
|
||||
<% unless internal_navigation? %>
|
||||
<meta name="last-updated" content="<%= Time.current %>">
|
||||
<meta name="user-signed-in" content="<%= user_signed_in? %>">
|
||||
<meta name="head-cached-at" content="<%= Time.current.to_i %>">
|
||||
<% if SiteConfig.payment_pointer.present? %>
|
||||
<!-- Experimental web monetization payment pointer for micropayments -->
|
||||
<!-- It lets readers make micropayments to websites they visit. -->
|
||||
<!-- This is step 1: Get live in production to test for platform-wide payment pointer. -->
|
||||
<!-- Step 2: Allow authors to set their payment pointer so they can directly monetize their content based on visitors. -->
|
||||
<!-- Step 3: Enable further functionality based on what we learn from this experimentation and how the ecosystem evolves. -->
|
||||
<meta name="monetization" content="<%= SiteConfig.payment_pointer %>">
|
||||
<% end %>
|
||||
<meta name="environment" content="<%= Rails.env %>">
|
||||
<%= render "layouts/styles", qualifier: "main" %>
|
||||
<% unless user_signed_in? %>
|
||||
<%= javascript_packs_with_chunks_tag "base", "Search", defer: true %>
|
||||
<% end %>
|
||||
<%= javascript_include_tag "base", defer: true %>
|
||||
<% if user_signed_in? %>
|
||||
<%= javascript_packs_with_chunks_tag "base", "Search", "onboardingRedirectCheck", "contentDisplayPolicy", defer: true %>
|
||||
<% end %>
|
||||
<%= yield(:page_meta) %>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
||||
<%= favicon_link_tag Images::Optimizer.call(SiteConfig.favicon_url, width: 32) %>
|
||||
<link rel="apple-touch-icon" href="<%= optimized_image_url(SiteConfig.logo_png, width: 180, fetch_format: "png") %>">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="<%= optimized_image_url(SiteConfig.logo_png, width: 152, fetch_format: "png") %>">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="<%= optimized_image_url(SiteConfig.logo_png, width: 180, fetch_format: "png") %>">
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="<%= optimized_image_url(SiteConfig.logo_png, width: 167, fetch_format: "png") %>">
|
||||
<link href="<%= optimized_image_url(SiteConfig.logo_png, width: 192, fetch_format: "png") %>" rel="icon" sizes="192x192" />
|
||||
<link href="<%= optimized_image_url(SiteConfig.logo_png, width: 128, fetch_format: "png") %>" rel="icon" sizes="128x128" />
|
||||
<meta name="apple-mobile-web-app-title" content="<%= SiteConfig.app_domain %>">
|
||||
<meta name="application-name" content="<%= SiteConfig.app_domain %>">
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<link rel="search" href="<%= URL.url("open-search.xml") %>" type="application/opensearchdescription+xml" title="<%= community_name %>" />
|
||||
|
||||
<meta property="forem:name" content="<%= community_name %>" />
|
||||
<meta property="forem:logo" content="<%= optimized_image_url(SiteConfig.logo_png, width: 512, fetch_format: "png") %>" />
|
||||
<meta property="forem:domain" content="<%= SiteConfig.app_domain %>" />
|
||||
<% end %>
|
||||
</head>
|
||||
<% unless internal_navigation? %>
|
||||
<% cache(release_adjusted_cache_key("top-html-and-config--#{user_signed_in?}")) do %>
|
||||
<body
|
||||
data-user-status="<%= user_logged_in_status %>"
|
||||
class="<%= SiteConfig.default_font.tr("_", "-") %>-article-body default-header"
|
||||
data-pusher-key="<%= ApplicationConfig["PUSHER_KEY"] %>"
|
||||
data-app-domain="<%= ChatChannel.urlsafe_encoded_app_domain %>"
|
||||
data-honeybadger-key="<%= ApplicationConfig["HONEYBADGER_JS_API_KEY"] %>"
|
||||
data-deployed-at="<%= ForemInstance.deployed_at %>"
|
||||
data-latest-commit-id="<%= ForemInstance.latest_commit_id %>"
|
||||
data-ga-tracking="<%= SiteConfig.ga_tracking_id %>">
|
||||
<%# Repeat of stylesheets in <head> to fix rendering glitch: https://github.com/forem/forem/issues/12377 %>
|
||||
<%= render "layouts/styles", qualifier: "secondary" %>
|
||||
<div id="body-styles">
|
||||
<style>
|
||||
:root {
|
||||
--accent-brand: <%= SiteConfig.primary_brand_color_hex %>;
|
||||
--accent-brand-darker: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).brightness(0.85) %>;
|
||||
--accent-brand-lighter: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).brightness(1.1) %>;
|
||||
--accent-brand-a10: <%= Color::CompareHex.new([SiteConfig.primary_brand_color_hex]).opacity(0.1) %>;
|
||||
}
|
||||
</style>
|
||||
</div>
|
||||
<% if user_signed_in? %>
|
||||
<%= render "layouts/user_config" %>
|
||||
<% end %>
|
||||
<div id="audiocontent" data-podcast="">
|
||||
<%= yield(:audio) %>
|
||||
</div>
|
||||
<%= render "layouts/top_bar" %>
|
||||
<div id="active-broadcast" class="broadcast-wrapper"></div>
|
||||
<div id="message-notice"></div>
|
||||
<div class="app-shell-loader">
|
||||
loading...
|
||||
</div>
|
||||
<% end %>
|
||||
<!-- End Top Shell -->
|
||||
<% end %>
|
||||
|
|
@ -577,9 +577,6 @@ Rails.application.routes.draw do
|
|||
get "/open-search", to: "open_search#show",
|
||||
constraints: { format: /xml/ }
|
||||
|
||||
get "/shell_top", to: "shell#top"
|
||||
get "/shell_bottom", to: "shell#bottom"
|
||||
|
||||
get "/new", to: "articles#new"
|
||||
get "/new/:template", to: "articles#new"
|
||||
|
||||
|
|
|
|||
|
|
@ -44,12 +44,3 @@ selectors more sophisticated than a simple id, class or tag name.
|
|||
- [Forem PR 6380](https://github.com/forem/forem/issues/6380#issuecomment-592989438)
|
||||
- [Why is getElementsByTagName() faster than querySelectorAll()?](https://humanwhocodes.com/blog/2010/09/28/why-is-getelementsbytagname-faster-that-queryselectorall/)
|
||||
- [What is the difference between querySelectorAll and getElementsByTagName?](https://stackoverflow.com/a/30921553/4186181)
|
||||
|
||||
## Service workers in development
|
||||
|
||||
By default our
|
||||
[service worker code](https://github.com/forem/forem/blob/master/app/views/service_worker/index.js.erb)
|
||||
doesn't run in development.
|
||||
|
||||
If you're planning to do any work around service workers you'll need to enable
|
||||
them by setting the `SKIP_SERVICEWORKERS` environment variable to `"false"`.
|
||||
|
|
|
|||
|
|
@ -25,18 +25,18 @@ caching.
|
|||
## Content precision
|
||||
|
||||
In some situations we may want more precise content than in others. Often when
|
||||
we do not need a precise number, it offers an opportunity to either estimate
|
||||
the content or bust the cache less frequently.
|
||||
we do not need a precise number, it offers an opportunity to either estimate the
|
||||
content or bust the cache less frequently.
|
||||
|
||||
### Examples
|
||||
|
||||
- We use the `estimated_count` for a more efficient query of registered users on
|
||||
the home page. We have deemed that this is probably close enough.
|
||||
- On posts and comment trees without recent comments, we do not asynchronously fetch
|
||||
the absolute latest individual reaction counts for logged-out users because this
|
||||
number is likely to be correct without the async call, and if it is off-by-one, we
|
||||
can make the choice that it is not important that it be more precise than this.
|
||||
|
||||
the home page. We have deemed that this is probably close enough.
|
||||
- On posts and comment trees without recent comments, we do not asynchronously
|
||||
fetch the absolute latest individual reaction counts for logged-out users
|
||||
because this number is likely to be correct without the async call, and if it
|
||||
is off-by-one, we can make the choice that it is not important that it be more
|
||||
precise than this.
|
||||
|
||||
## We Mostly defer scripts for usage performance improvements
|
||||
|
||||
|
|
@ -54,16 +54,6 @@ We use [PreactJS](/frontend/preact), a lightweight alternative to ReactJS, and
|
|||
we try to reduce our bundle size with
|
||||
[dynamic imports](/frontend/dynamic-imports).
|
||||
|
||||
## Service workers and shell architecture
|
||||
|
||||
We make use of serviceworkers to cache portions of the page.
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -4,12 +4,6 @@ namespace :cache do
|
|||
# Trigger cache purges for globally-cached endpoints that could have changed
|
||||
[30, 180, 600].each do |n|
|
||||
BustCachePathWorker.set(queue: :high_priority).perform_in(n.seconds, "/")
|
||||
# TODO: Remove these "shell" endpoints, because they are for service worker functionality we no longer need.
|
||||
# We are keeping these around mid-March 2021 because previously-installed service workers may still expect them.
|
||||
BustCachePathWorker.set(queue: :high_priority).perform_in(n.seconds, "/shell_top")
|
||||
BustCachePathWorker.set(queue: :high_priority).perform_in(n.seconds, "/shell_bottom")
|
||||
BustCachePathWorker.set(queue: :high_priority).perform_in(n.seconds, "/async_info/shell_version")
|
||||
|
||||
BustCachePathWorker.set(queue: :high_priority).perform_in(n.seconds, "/onboarding")
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,360 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>You are not connected to the Internet</title>
|
||||
<meta property="og:url" content="https://dev.to/offline.html" />
|
||||
<meta property="og:title" content="Looks like you've lost your Internet connection" />
|
||||
<meta property="og:image" content="https://thepracticaldev.s3.amazonaws.com/i/aio6nc08tpcqavej512d.png" />
|
||||
<meta property="og:description" content="Maybe you could go outside and get some fresh air." />
|
||||
<meta property="og:site_name" content="The DEV Community" />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:site" content="@ThePracticalDev">
|
||||
<meta name="twitter:title" content="Looks like you've lost your Internet connection">
|
||||
<meta name="twitter:description" content="Maybe you could go outside and get some fresh air.">
|
||||
<meta name="twitter:image:src" content="https://thepracticaldev.s3.amazonaws.com/i/aio6nc08tpcqavej512d.png">
|
||||
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
canvas {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
body {
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-family: -apple-system, system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
}
|
||||
|
||||
h2,
|
||||
h3 {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
canvas {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
path {
|
||||
opacity: 50%;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
margin: calc(25px + 2vw) 2vw;
|
||||
}
|
||||
|
||||
.paletteSelector {
|
||||
z-index: 2;
|
||||
margin-top: 30px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#btn-clear, .paletteSelector select {
|
||||
height: 30px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.colors {
|
||||
z-index: 2;
|
||||
margin: 0 20px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
min-height: 52px;
|
||||
}
|
||||
|
||||
.color {
|
||||
height: 50px;
|
||||
width: 50px;
|
||||
margin-right: 10px;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.color:hover {
|
||||
opacity: .7;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.color:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.rainbow-logo {
|
||||
border-radius: 10%;
|
||||
max-width: 350px;
|
||||
max-height: 350px;
|
||||
}
|
||||
|
||||
.signature {
|
||||
margin: 20px auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.crayons-footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-height: 700px) {
|
||||
h2 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.0em;
|
||||
}
|
||||
|
||||
.color {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
}
|
||||
|
||||
.signature {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas></canvas>
|
||||
<div class="content">
|
||||
<svg viewBox="0 0 235 234" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="rainbow-logo"
|
||||
preserveAspectRatio="xMinYMin meet">
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="80K">
|
||||
<polygon id="Shape" fill="#88AEDC" points="234.04 175.67 158.35 233.95 205.53 233.95 234.04 212"></polygon>
|
||||
<polygon id="Shape" points="234.04 140.06 112.11 233.95 112.13 233.95 234.04 140.08"></polygon>
|
||||
<polygon id="Shape" points="133.25 0.95 0.04 103.51 0.04 103.53 133.27 0.95"></polygon>
|
||||
<polygon id="Shape" fill="#F58F8E" fill-rule="nonzero" points="0.04 0.95 0.04 31.11 39.21 0.95"></polygon>
|
||||
<polygon id="Shape" fill="#FEE18A" fill-rule="nonzero" points="39.21 0.95 0.04 31.11 0.04 67.01 85.84 0.95"></polygon>
|
||||
<polygon id="Shape" fill="#F3F095" fill-rule="nonzero" points="85.84 0.95 0.04 67.01 0.04 103.51 133.25 0.95"></polygon>
|
||||
<polygon id="Shape" fill="#55C1AE" fill-rule="nonzero" points="133.27 0.95 0.04 103.53 0.04 139.12 179.49 0.95"></polygon>
|
||||
<polygon id="Shape" fill="#F7B3CE" fill-rule="nonzero" points="234.04 0.95 226.67 0.95 0.04 175.45 0.04 211.38 234.04 31.2"></polygon>
|
||||
<polygon id="Shape" fill="#88AEDC" fill-rule="nonzero" points="179.49 0.95 0.04 139.12 0.04 175.45 226.67 0.95"></polygon>
|
||||
<polygon id="Shape" fill="#F58F8E" fill-rule="nonzero" points="234.04 31.2 0.04 211.38 0.04 233.95 18.07 233.95 234.04 67.65"></polygon>
|
||||
<polygon id="Shape" fill="#FEE18A" fill-rule="nonzero" points="234.04 67.65 18.07 233.95 64.7 233.95 234.04 103.56"></polygon>
|
||||
<polygon id="Shape" fill="#F3F095" fill-rule="nonzero" points="234.04 103.56 64.7 233.95 112.11 233.95 234.04 140.06"></polygon>
|
||||
<polygon id="Shape" fill="#55C1AE" fill-rule="nonzero" points="234.04 140.08 112.13 233.95 158.35 233.95 234.04 175.67"></polygon>
|
||||
<polygon id="Shape" fill="#F7B3CE" fill-rule="nonzero" points="234.04 212 205.53 233.95 234.04 233.95"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<h2> So, there's good news and bad news</h2>
|
||||
<h3>Bad news: It looks like you're offline</h3>
|
||||
<h3>Good news: You can draw a picture anywhere on this page while you wait to get it back!</h3>
|
||||
<h4>(pick a color below && start drawing!)</h4>
|
||||
</div>
|
||||
|
||||
<div class="colors">
|
||||
</div>
|
||||
<div class='paletteSelector'>
|
||||
<select id="palettes"></select>
|
||||
<button type="button" id="btn-clear">Clear</button>
|
||||
</div>
|
||||
<svg width="75px" height="75px" viewBox="0 0 266 286" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
class="signature">
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Artboard-3" transform="translate(-66.000000, -39.000000)" stroke="#F4908E" stroke-width="8">
|
||||
<g id="Group" transform="translate(70.000000, 44.000000)">
|
||||
<path d="M137.947881,11.0969119 C120.236214,10.7929621 127.259584,-4.63518682 110.600707,1.39809583 C98.4900537,5.78416547 5.38963223,31.9682026 1.99251552,84.34539 C0.138615017,112.929069 -0.563473858,141.639852 0.495802997,170.264134 C0.973626724,183.176113 16.3154795,204.585393 22.2134768,211.816457 C35.0843735,227.596436 61.6452079,206.201192 70.9197071,196.000824 C104.380023,159.200179 133.244668,118.47588 137.947881,67.1534449 C138.600896,60.027607 145.952931,5.41350351 126.011684,11.0969119 C112.376346,14.9830876 103.436568,65.3726702 102.950719,78.3517738 C101.357143,120.922959 86.4228272,211.00328 146.955431,222.813291 C172.852291,227.865812 169.286943,226.931981 192,226.931981"
|
||||
id="Path-2"></path>
|
||||
<path d="M80,277.169785 C120.408772,252.192233 121.434199,237.685468 133.304963,222.650446 C151.591469,199.489509 173.572805,172.509583 233.39048,84.1055622 C240.794147,73.1637472 242.286005,30.9559106 238.678367,28.2608272 C236.630233,26.7307705 219.030597,13.826187 211.887732,18.9306581 C202.734845,25.4715418 203.241886,57.600262 203.223682,60.0372493 C203.010014,88.6406571 203.914879,117.248834 205.052575,145.830265 C206.126657,172.81357 211.080162,198.940616 208.995298,226.072378 C206.84232,254.090552 155.519583,271.514023 133.304963,268.076421 C126.954422,267.093706 93.5574761,243.825981 102.987596,240.93683 C113.776095,237.631506 146.615548,245.587641 160.390474,240.93683 C174.165399,236.286019 182.361602,234.057591 192.833448,229.336442 C203.454817,224.547881 249.685075,198.800486 259.8975,193.187538"
|
||||
id="Path-3"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<script type="text/javascript">
|
||||
|
||||
const palettes = {
|
||||
default: {
|
||||
name: 'Default Palette',
|
||||
colors: ['#F4908E', '#F2F097', '#88B0DC', '#F7B5D1', '#53C4AF', '#FDE38C']
|
||||
},
|
||||
// Color-deficient accessible palettes: https://owi.usgs.gov/blog/tolcolors/
|
||||
paulTol: {
|
||||
name: 'Paul Tol Palette',
|
||||
colors: ['#B997C6', '#824D99', '#4E79C4', '#57A2AC', '#7EB875', '#D0B440', '#E67F33']
|
||||
},
|
||||
// Add more palette options below and to the "select" dropdown.
|
||||
}
|
||||
let activePalette = 'default' // match key inside the palettes variable
|
||||
|
||||
// Globally DOM nodes + variables
|
||||
const paletteSelector = document.querySelector('.paletteSelector')
|
||||
const colorSelector = document.querySelector('.colors')
|
||||
const canvas = document.querySelector('canvas')
|
||||
const context = canvas.getContext('2d')
|
||||
const colorDiv = document.querySelector('.colors')
|
||||
let dx = 1, dy = 1;
|
||||
|
||||
// handler for color input buttons
|
||||
function handleButtonClick(event) {
|
||||
const buttonStyle = event.target.style
|
||||
context.strokeStyle = buttonStyle.backgroundColor
|
||||
}
|
||||
|
||||
// Main drawing function
|
||||
function renderColorButtons(colors) {
|
||||
// First, remove existing event listeners to prevent memory leak
|
||||
colorDiv.querySelectorAll('button').forEach((button) => {
|
||||
button.removeEventListener('click', handleButtonClick)
|
||||
})
|
||||
// Then, remove existing buttons
|
||||
colorDiv.innerHTML = ''
|
||||
|
||||
// Lastly, add new a new button for each color in the passed in palette
|
||||
colors.forEach(color => {
|
||||
const button = document.createElement('button')
|
||||
button.classList.add('color')
|
||||
button.style.backgroundColor = color
|
||||
colorDiv.appendChild(button)
|
||||
button.addEventListener('click', handleButtonClick)
|
||||
})
|
||||
}
|
||||
|
||||
// Helper for modifying canvas context
|
||||
const setContextSettings = (strokeColor) => {
|
||||
context.strokeStyle = strokeColor
|
||||
context.lineJoin = 'round'
|
||||
context.lineWidth = 5
|
||||
}
|
||||
|
||||
const setCanvasSize = () => {
|
||||
// set dimensions on the canvas
|
||||
canvas.setAttribute('width', window.innerWidth)
|
||||
canvas.setAttribute('height', window.innerHeight)
|
||||
}
|
||||
|
||||
const setResizeFactors = () => {
|
||||
// set the x and y resizinf factor for cursor position
|
||||
dx = canvas.width / window.innerWidth
|
||||
dy = canvas.height / window.innerHeight
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
setCanvasSize()
|
||||
setResizeFactors();
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
// Main function to render new DOM whenever activePalette changes
|
||||
const render = (palette) => {
|
||||
renderColorButtons(palette)
|
||||
setContextSettings(palette[0])
|
||||
setCanvasSize()
|
||||
}
|
||||
|
||||
// User Input Event Handlers
|
||||
//----------------------------------------
|
||||
let firstX, firstY, secondX, secondY, paint
|
||||
|
||||
function getCoordinates(event) {
|
||||
// check to see if mobile or desktop
|
||||
if (['mousedown', 'mousemove'].includes(event.type)) {
|
||||
// click events
|
||||
return [event.pageX * dx, event.pageY * dy]
|
||||
} else {
|
||||
// touch coordinates
|
||||
return [event.touches[0].pageX * dx, event.touches[0].pageY * dy]
|
||||
}
|
||||
}
|
||||
|
||||
function addClick(event, canvas) {
|
||||
let [x, y] = getCoordinates(event)
|
||||
|
||||
secondX = firstX
|
||||
secondY = firstY
|
||||
firstX = x
|
||||
firstY = y
|
||||
}
|
||||
|
||||
function draw() {
|
||||
context.beginPath()
|
||||
context.moveTo(secondX, secondY)
|
||||
context.lineTo(firstX, firstY)
|
||||
context.closePath()
|
||||
context.stroke()
|
||||
}
|
||||
|
||||
function startPaint(event) {
|
||||
colorSelector.style.pointerEvents = 'none'
|
||||
paletteSelector.style.pointerEvents = 'none'
|
||||
|
||||
addClick(event, this)
|
||||
paint = true
|
||||
}
|
||||
canvas.addEventListener('mousedown', startPaint)
|
||||
canvas.addEventListener('touchstart', startPaint)
|
||||
|
||||
const exit = _ => {
|
||||
colorSelector.style.pointerEvents = 'all'
|
||||
paletteSelector.style.pointerEvents = 'all'
|
||||
|
||||
paint = false
|
||||
firstX = null
|
||||
firstY = null
|
||||
}
|
||||
canvas.addEventListener('mouseup', exit)
|
||||
canvas.addEventListener('mouseleave', exit)
|
||||
canvas.addEventListener('touchend', exit)
|
||||
|
||||
function endPaint(event) {
|
||||
if (paint) {
|
||||
addClick(event, this)
|
||||
draw()
|
||||
}
|
||||
}
|
||||
canvas.addEventListener('mousemove', endPaint)
|
||||
canvas.addEventListener('touchmove', endPaint)
|
||||
|
||||
// generate options for select tag
|
||||
let selectTag = document.getElementById('palettes');
|
||||
|
||||
Object.keys(palettes).forEach((key) => {
|
||||
// create tag
|
||||
let option = document.createElement('option');
|
||||
option.value = key;
|
||||
option.textContent = palettes[key].name;
|
||||
|
||||
// insert to select
|
||||
selectTag.appendChild(option);
|
||||
});
|
||||
|
||||
paletteSelector.onchange = (event) => {
|
||||
activePalette = event.target.value
|
||||
render(palettes[activePalette].colors)
|
||||
}
|
||||
|
||||
document.getElementById('btn-clear').addEventListener('click', (e) => {
|
||||
context.clearRect(0, 0, canvas.width, canvas.height);
|
||||
});
|
||||
|
||||
// Initialization when page is ready to load, runs once
|
||||
(function() {
|
||||
render(palettes[activePalette].colors)
|
||||
})()
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -139,20 +139,6 @@ RSpec.describe ApplicationHelper, type: :helper do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#sanitized_referer" do
|
||||
it "returns a safe referrer unmodified" do
|
||||
expect(sanitized_referer("/some/path")).to eq("/some/path")
|
||||
end
|
||||
|
||||
it "returns nil if the referer is the service worker" do
|
||||
expect(sanitized_referer("/serviceworker.js")).to be nil
|
||||
end
|
||||
|
||||
it "returns nil if the referer is empty" do
|
||||
expect(sanitized_referer("")).to be nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#collection_link" do
|
||||
let(:collection) { create(:collection, :with_articles) }
|
||||
|
||||
|
|
|
|||
|
|
@ -246,20 +246,6 @@ RSpec.describe "Pages", type: :request do
|
|||
get "/report-abuse", headers: { referer: url }
|
||||
expect(response.body).to include(url)
|
||||
end
|
||||
|
||||
it "does not prefill if the provide url is /serviceworker.js" do
|
||||
url = "https://dev.to/serviceworker.js"
|
||||
get "/report-abuse", headers: { referer: url }
|
||||
expect(response.body).not_to include(url)
|
||||
end
|
||||
end
|
||||
|
||||
context "when provided the params" do
|
||||
it "prefills with the provided param url" do
|
||||
url = "https://dev.to/serviceworker.js"
|
||||
get "/report-abuse", params: { url: url }
|
||||
expect(response.body).to include(url)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
require "rails_helper"
|
||||
|
||||
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("Service worker file")
|
||||
end
|
||||
|
||||
it "renders javascript file" do
|
||||
get "/serviceworker.js"
|
||||
expect(response.header["Content-Type"]).to include("text/javascript")
|
||||
end
|
||||
|
||||
it "sends a surrogate key (for Fastly's user)" do
|
||||
get "/serviceworker.js"
|
||||
expect(response.header["Surrogate-Key"]).to include("serviceworker-js")
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /manifest.json" do
|
||||
it "renders file with proper text" do
|
||||
get "/manifest.json"
|
||||
expect(response.body).to include("\"name\": \"#{SiteConfig.community_name}\"")
|
||||
end
|
||||
|
||||
it "renders json file" do
|
||||
get "/manifest.json"
|
||||
expect(response.header["Content-Type"]).to include("application/json")
|
||||
end
|
||||
|
||||
it "sends a surrogate key (for Fastly's user)" do
|
||||
get "/manifest.json"
|
||||
expect(response.header["Surrogate-Key"]).to include("manifest-json")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe "Shells", type: :request do
|
||||
describe "GET /shell_top" do
|
||||
it "renders file with proper text" do
|
||||
get "/shell_top"
|
||||
expect(response.body).to include("user-signed-in")
|
||||
end
|
||||
|
||||
it "sends a surrogate key (for Fastly's user)" do
|
||||
get "/shell_top"
|
||||
expect(response.header["Surrogate-Key"]).to include("shell-top")
|
||||
end
|
||||
|
||||
it "renders normal response even if site config is private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
get "/shell_top"
|
||||
expect(response.body).to include("user-signed-in")
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /shell_bottom" do
|
||||
it "renders file with proper text" do
|
||||
get "/shell_bottom"
|
||||
expect(response.body).to include("footer-container")
|
||||
end
|
||||
|
||||
it "sends a surrogate key (for Fastly's user)" do
|
||||
get "/shell_bottom"
|
||||
expect(response.header["Surrogate-Key"]).to include("shell-bottom")
|
||||
end
|
||||
|
||||
it "renders normal response even if site config is private" do
|
||||
allow(SiteConfig).to receive(:public).and_return(false)
|
||||
get "/shell_bottom"
|
||||
expect(response.body).to include("footer-container")
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue