diff --git a/app/javascript/chat/__tests__/util.test.js b/app/javascript/chat/__tests__/util.test.js new file mode 100644 index 000000000..34aeca376 --- /dev/null +++ b/app/javascript/chat/__tests__/util.test.js @@ -0,0 +1,109 @@ +import { getUserDataAndCsrfToken } from '../util'; + +const ERROR_MESSAGE = "Couldn't find user data on page."; + +describe('Chat utilities', () => { + describe('getUserDataAndCsrfToken', () => { + afterEach(() => { + document.head.innerHTML = ''; + document.body.removeAttribute('data-user'); + }); + + test('should reject if no user or csrf token found.', async () => { + await expect(getUserDataAndCsrfToken(document)).rejects.toThrow( + ERROR_MESSAGE, + ); + }); + + test('should reject if csrf token found but no user.', async () => { + document.head.innerHTML = + ''; + await expect(getUserDataAndCsrfToken(document)).rejects.toThrow( + ERROR_MESSAGE, + ); + }); + + test('should reject if user found but no csrf token found.', async () => { + document.body.setAttribute('data-user', '{}'); + await expect(getUserDataAndCsrfToken(document)).rejects.toThrow( + ERROR_MESSAGE, + ); + }); + + test('should resolve if user and csrf token found.', () => { + const csrfToken = 'some-csrf-token'; + const currentUser = { + id: 41, + name: 'Guy Fieri', + username: 'guyfieri', + profile_image_90: + '/uploads/user/profile_image/41/0841dbe2-208c-4daa-b498-b2f01f3d37b2.png', + followed_tag_names: [], + followed_tags: '[]', + followed_user_ids: [ + 2, + 31, + 7, + 38, + 22, + 37, + 14, + 26, + 20, + 24, + 11, + 27, + 29, + 3, + 6, + 28, + 4, + 39, + 8, + 40, + 25, + 30, + 35, + 34, + 5, + 12, + 33, + 36, + 21, + 18, + 23, + 1, + 32, + 19, + 15, + 13, + 16, + 9, + 10, + 17, + ], + reading_list_ids: [48, 49, 34, 51, 64, 56], + saw_onboarding: true, + onboarding_checklist: { + write_your_first_article: false, + follow_your_first_tag: false, + fill_out_your_profile: false, + leave_your_first_reaction: true, + follow_your_first_dev: true, + leave_your_first_comment: false, + }, + checked_code_of_conduct: false, + number_of_comments: 0, + display_sponsors: true, + trusted: false, + }; + document.head.innerHTML = ``; + document.body.setAttribute('data-user', JSON.stringify(currentUser)); + + expect(getUserDataAndCsrfToken(document)).resolves.toEqual({ + currentUser, + csrfToken, + }); + }); + }); +}); diff --git a/app/javascript/chat/util.js b/app/javascript/chat/util.js index 4d508daf0..f21bbfd5f 100644 --- a/app/javascript/chat/util.js +++ b/app/javascript/chat/util.js @@ -1,35 +1,40 @@ import 'intersection-observer'; import { sendKeys } from './actions'; +export function getCsrfToken() { + const element = document.querySelector(`meta[name='csrf-token']`); + + return element !== null ? element.content : undefined; +} + +const getWaitOnUserDataHandler = ({ resolve, reject, waitTime = 20 }) => { + let totalTimeWaiting = 0; + + return function waitingOnUserData() { + if (totalTimeWaiting === 3000) { + reject(new Error("Couldn't find user data on page.")); + return; + } + + const csrfToken = getCsrfToken(document); + const { user } = document.body.dataset; + + if (user && csrfToken !== undefined) { + const currentUser = JSON.parse(user); + + resolve({ currentUser, csrfToken }); + return; + } + + totalTimeWaiting += waitTime; + setTimeout(waitingOnUserData, waitTime); + }; +}; export function getUserDataAndCsrfToken() { - const promise = new Promise((resolve, reject) => { - let i = 0; - const waitingOnUserData = setInterval(() => { - let userData = null; - const dataUserAttribute = document.body.getAttribute('data-user'); - const meta = document.querySelector("meta[name='csrf-token']"); - if ( - dataUserAttribute && - dataUserAttribute !== 'undefined' && - dataUserAttribute !== undefined && - meta && - meta.content !== 'undefined' && - meta.content !== undefined - ) { - userData = JSON.parse(dataUserAttribute); - } - i += 1; - if (userData) { - clearInterval(waitingOnUserData); - resolve(userData); - } else if (i === 3000) { - clearInterval(waitingOnUserData); - reject(new Error("Couldn't find user data on page.")); - } - }, 5); + return new Promise((resolve, reject) => { + getWaitOnUserDataHandler({ resolve, reject })(); }); - return promise; } export function scrollToBottom() { @@ -76,39 +81,41 @@ export function adjustTimestamp(timestamp) { return time; } - export function setupNotifications() { - navigator.serviceWorker.ready.then((serviceWorkerRegistration) => { - serviceWorkerRegistration.pushManager.getSubscription() - .then(function(subscription) { + navigator.serviceWorker.ready.then(serviceWorkerRegistration => { + serviceWorkerRegistration.pushManager + .getSubscription() + .then(subscription => { if (subscription) { return subscription; } return serviceWorkerRegistration.pushManager.subscribe({ userVisibleOnly: true, - applicationServerKey: window.vapidPublicKey + applicationServerKey: window.vapidPublicKey, }); - }).then(function(subscription) { - sendKeys(subscription.toJSON(), null, null) + }) + .then(subscription => { + sendKeys(subscription.toJSON(), null, null); }); }); } export function getNotificationState() { - //Not yet ready - if (!window.location.href.includes('ask-for-notifications')){ - return "dont-ask" + // Not yet ready + if (!window.location.href.includes('ask-for-notifications')) { + return 'dont-ask'; } // Let's check if the browser supports notifications - if (!("Notification" in window)) { - return "not-supported" - } else if (Notification.permission === "granted") { - setupNotifications(); - return "granted" - } else if (Notification.permission !== 'denied') { - return "waiting-permission" - } else { - return "denied" + if (!('Notification' in window)) { + return 'not-supported'; } -} \ No newline at end of file + + const { permission } = Notification; + + if (permission === 'granted') { + setupNotifications(); + } + + return permission === 'default' ? 'waiting-permission' : permission; +} diff --git a/app/javascript/packs/Chat.jsx b/app/javascript/packs/Chat.jsx new file mode 100644 index 000000000..a9c652c92 --- /dev/null +++ b/app/javascript/packs/Chat.jsx @@ -0,0 +1,49 @@ +import { h, render } from 'preact'; +import { getUserDataAndCsrfToken } from '../chat/util'; +import Chat from '../chat/chat'; + +function initializeChat(loadChat) { + getUserDataAndCsrfToken() + .then(loadChat) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Unable to load chat', error); + }); +} + +function renderChat(root) { + render(, root, root.firstChild); +} + +const loadChat = ({ currentUser, csrfToken }) => { + const root = document.getElementById('chat'); + + if (!root) { + return; + } + + window.currentUser = currentUser; + window.csrfToken = csrfToken; + + const placeholder = document.getElementById('chat_placeholder'); + + renderChat(root); + + if (placeholder) { + root.removeChild(placeholder); + } + + window.InstantClick.on('change', () => { + if (window.location.href.indexOf('/connect') === -1) { + return; + } + + renderChat(root); + }); +}; + +initializeChat(loadChat); + +window.InstantClick.on('change', () => { + initializeChat(loadChat); +}); diff --git a/app/javascript/packs/Onboarding.jsx b/app/javascript/packs/Onboarding.jsx new file mode 100644 index 000000000..fd5d5663d --- /dev/null +++ b/app/javascript/packs/Onboarding.jsx @@ -0,0 +1,48 @@ +import { h, render } from 'preact'; +import { getUserDataAndCsrfToken } from '../chat/util'; +import getUnopenedChannels from '../src/utils/getUnopenedChannels'; + +HTMLDocument.prototype.ready = new Promise(resolve => { + if (document.readyState !== 'loading') { + return resolve(); + } + document.addEventListener('DOMContentLoaded', () => resolve()); + return null; +}); + +function isUserSignedIn() { + return ( + document.head.querySelector( + 'meta[name="user-signed-in"][content="true"]', + ) !== null + ); +} + +function renderPage() { + import('../src/Onboarding') + .then(({ default: Onboarding }) => + render(, document.getElementById('top-bar')), + ) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Unable to load onboarding', error); + }); +} + +document.ready.then( + getUserDataAndCsrfToken() + .then(({ currentUser, csrfToken }) => { + window.currentUser = currentUser; + window.csrfToken = csrfToken; + + getUnopenedChannels(); + + if (isUserSignedIn() && !currentUser.saw_onboarding) { + renderPage(); + } + }) + .catch(error => { + // eslint-disable-next-line no-console + console.error('Error getting user and CSRF Token', error); + }), +); diff --git a/app/javascript/packs/articleForm.jsx b/app/javascript/packs/articleForm.jsx index c3c3fd06e..0135abebe 100644 --- a/app/javascript/packs/articleForm.jsx +++ b/app/javascript/packs/articleForm.jsx @@ -11,19 +11,17 @@ HTMLDocument.prototype.ready = new Promise(resolve => { }); document.ready.then( - getUserDataAndCsrfToken().then(currentUser => { + getUserDataAndCsrfToken().then(({ currentUser, csrfToken }) => { window.currentUser = currentUser; + window.csrfToken = csrfToken; + const root = document.getElementById('article-form'); - window.csrfToken = document.querySelector( - "meta[name='csrf-token']", - ).content; + const { article, organization } = root.dataset; + render( - , + , root, - root.firstElementChild + root.firstElementChild, ); }), ); diff --git a/app/javascript/packs/chat.jsx b/app/javascript/packs/chat.jsx deleted file mode 100644 index 0d265f26b..000000000 --- a/app/javascript/packs/chat.jsx +++ /dev/null @@ -1,71 +0,0 @@ -import { h, render } from 'preact'; -import { getUserDataAndCsrfToken } from '../chat/util'; -import Chat from '../chat/chat'; - -HTMLDocument.prototype.ready = new Promise(resolve => { - if (document.readyState !== 'loading') { - return resolve(); - } - document.addEventListener('DOMContentLoaded', () => resolve()); - return null; -}); - -loadChat(); // Load initially -window.InstantClick.on('change', () => { - loadChat(); // Load via instantclick nav -}); - -function loadChat() { - getUserDataAndCsrfToken().then(currentUser => { - if (document.getElementById('chat')) { - const { - chatChannels, - pusherKey, - chatOptions, - algoliaKey, - algoliaId, - algoliaIndex, - githubToken, - } = document.getElementById('chat').dataset; - window.currentUser = currentUser; - window.csrfToken = document.querySelector( - "meta[name='csrf-token']", - ).content; - const renderTarget = document.getElementById('chat'); - const placeholder = document.getElementById('chat_placeholder'); - const root = render( - , - renderTarget, - renderTarget.firstChild, - ); - if (placeholder) { - renderTarget.removeChild(placeholder); - } - window.InstantClick.on('change', () => { - if (window.location.href.indexOf('/connect') != -1) { - const root = render( - , - renderTarget, - renderTarget.firstChild, - ); - } - }); - } - }); -} diff --git a/app/javascript/packs/pack.js b/app/javascript/packs/pack.js deleted file mode 100644 index 08cd5e284..000000000 --- a/app/javascript/packs/pack.js +++ /dev/null @@ -1,40 +0,0 @@ -import { h, render } from 'preact'; -import Onboarding from '../src/Onboarding'; -import { getUserDataAndCsrfToken } from '../chat/util'; -import getUnopenedChannels from '../src/utils/getUnopenedChannels'; - -HTMLDocument.prototype.ready = new Promise(resolve => { - if (document.readyState !== 'loading') { - return resolve(); - } - document.addEventListener('DOMContentLoaded', () => resolve()); - return null; -}); - -function shouldShowOnboarding() { - return ( - document.head.getElementsByTagName('meta')[2].content === 'true' && - document.body.getAttribute('data-user') && - document.body.getAttribute('data-user') !== 'undefined' && - JSON.parse(document.body.getAttribute('data-user')).saw_onboarding === false - ); -} - -function renderPage() { - if (shouldShowOnboarding()) { - setTimeout(() => { - render(, document.getElementById('top-bar')); - }, 580); - } -} - -document.ready.then( - getUserDataAndCsrfToken().then(currentUser => { - window.currentUser = currentUser; - window.csrfToken = document.querySelector( - "meta[name='csrf-token']", - ).content; - renderPage(); - getUnopenedChannels(); - }), -); diff --git a/app/views/chat_channels/index.html.erb b/app/views/chat_channels/index.html.erb index 9de1f0b12..893c6087b 100644 --- a/app/views/chat_channels/index.html.erb +++ b/app/views/chat_channels/index.html.erb @@ -8,7 +8,7 @@ <% if user_signed_in? %> - <%= javascript_pack_tag 'chat', defer: true %> + <%= javascript_pack_tag 'Chat', defer: true %>
<%= javascript_include_tag 'base', defer: true %> <% if user_signed_in? %> - <%= javascript_pack_tag 'pack', defer: true %> + <%= javascript_pack_tag 'Onboarding', defer: true %> <% end %> <% end %> <% if !core_pages? %> diff --git a/app/views/pages/live.html.erb b/app/views/pages/live.html.erb index 201fb7215..7e23fa5e1 100644 --- a/app/views/pages/live.html.erb +++ b/app/views/pages/live.html.erb @@ -1,5 +1,5 @@ <% if user_signed_in? %> - <%= javascript_pack_tag 'chat', defer: true %> + <%= javascript_pack_tag 'Chat', defer: true %> <% end %> <% title "DEV Live 📡👩‍💻👨‍💻👩‍💻👨‍💻" %>