From abd9ebeb3533182e975bdcbd81b613f67257d3c7 Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Sat, 17 Nov 2018 19:09:19 -0800 Subject: [PATCH] ui: Use web components to render server-side HTML --- ui/src/Page/Home.elm | 4 +- ui/src/Page/Mailbox.elm | 4 +- ui/src/index.js | 1 + ui/src/registerServiceWorker.js | 108 -------------------------------- ui/src/renderedHtml.js | 24 +++++++ 5 files changed, 29 insertions(+), 112 deletions(-) delete mode 100644 ui/src/registerServiceWorker.js create mode 100644 ui/src/renderedHtml.js diff --git a/ui/src/Page/Home.elm b/ui/src/Page/Home.elm index 9b16a28..6dbaee2 100644 --- a/ui/src/Page/Home.elm +++ b/ui/src/Page/Home.elm @@ -60,9 +60,9 @@ update session msg model = view : Session -> Model -> Html Msg view session model = div [ id "page" ] - [ div + [ Html.node "rendered-html" [ class "greeting" - , property "innerHTML" (Encode.string model.greeting) + , property "content" (Encode.string model.greeting) ] [] ] diff --git a/ui/src/Page/Mailbox.elm b/ui/src/Page/Mailbox.elm index 45a679b..7efa70e 100644 --- a/ui/src/Page/Mailbox.elm +++ b/ui/src/Page/Mailbox.elm @@ -588,10 +588,10 @@ messageBody message bodyMode = , article [ class "message-body" ] [ case bodyMode of SafeHtmlBody -> - div [ property "innerHTML" (Encode.string message.html) ] [] + Html.node "rendered-html" [ property "content" (Encode.string message.html) ] [] TextBody -> - div [ property "innerHTML" (Encode.string message.text) ] [] + Html.node "rendered-html" [ property "content" (Encode.string message.text) ] [] ] ] diff --git a/ui/src/index.js b/ui/src/index.js index b3249be..e929226 100644 --- a/ui/src/index.js +++ b/ui/src/index.js @@ -1,6 +1,7 @@ import './main.css' import { Elm } from './Main.elm' import registerMonitorPorts from './registerMonitor' +import './renderedHtml' // App startup. var app = Elm.Main.init({ diff --git a/ui/src/registerServiceWorker.js b/ui/src/registerServiceWorker.js deleted file mode 100644 index 4a3ccf0..0000000 --- a/ui/src/registerServiceWorker.js +++ /dev/null @@ -1,108 +0,0 @@ -// In production, we register a service worker to serve assets from local cache. - -// This lets the app load faster on subsequent visits in production, and gives -// it offline capabilities. However, it also means that developers (and users) -// will only see deployed updates on the "N+1" visit to a page, since previously -// cached resources are updated in the background. - -// To learn more about the benefits of this model, read https://goo.gl/KwvDNy. -// This link also includes instructions on opting out of this behavior. - -const isLocalhost = Boolean( - window.location.hostname === 'localhost' || - // [::1] is the IPv6 localhost address. - window.location.hostname === '[::1]' || - // 127.0.0.1/8 is considered localhost for IPv4. - window.location.hostname.match( - /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ - ) -); - -export default function register() { - if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { - // The URL constructor is available in all browsers that support SW. - const publicUrl = new URL(process.env.PUBLIC_URL, window.location); - if (publicUrl.origin !== window.location.origin) { - // Our service worker won't work if PUBLIC_URL is on a different origin - // from what our page is served on. This might happen if a CDN is used to - // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374 - return; - } - - window.addEventListener('load', () => { - const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; - - if (!isLocalhost) { - // Is not local host. Just register service worker - registerValidSW(swUrl); - } else { - // This is running on localhost. Lets check if a service worker still exists or not. - checkValidServiceWorker(swUrl); - } - }); - } -} - -function registerValidSW(swUrl) { - navigator.serviceWorker - .register(swUrl) - .then(registration => { - registration.onupdatefound = () => { - const installingWorker = registration.installing; - installingWorker.onstatechange = () => { - if (installingWorker.state === 'installed') { - if (navigator.serviceWorker.controller) { - // At this point, the old content will have been purged and - // the fresh content will have been added to the cache. - // It's the perfect time to display a "New content is - // available; please refresh." message in your web app. - console.log('New content is available; please refresh.'); - } else { - // At this point, everything has been precached. - // It's the perfect time to display a - // "Content is cached for offline use." message. - console.log('Content is cached for offline use.'); - } - } - }; - }; - }) - .catch(error => { - console.error('Error during service worker registration:', error); - }); -} - -function checkValidServiceWorker(swUrl) { - // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl) - .then(response => { - // Ensure service worker exists, and that we really are getting a JS file. - if ( - response.status === 404 || - response.headers.get('content-type').indexOf('javascript') === -1 - ) { - // No service worker found. Probably a different app. Reload the page. - navigator.serviceWorker.ready.then(registration => { - registration.unregister().then(() => { - window.location.reload(); - }); - }); - } else { - // Service worker found. Proceed as normal. - registerValidSW(swUrl); - } - }) - .catch(() => { - console.log( - 'No internet connection found. App is running in offline mode.' - ); - }); -} - -export function unregister() { - if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready.then(registration => { - registration.unregister(); - }); - } -} diff --git a/ui/src/renderedHtml.js b/ui/src/renderedHtml.js new file mode 100644 index 0000000..62b04ad --- /dev/null +++ b/ui/src/renderedHtml.js @@ -0,0 +1,24 @@ +// This element allows Inbucket to draw server rendered HTML, aka HTML email. +// https://leveljournal.com/server-rendered-html-in-elm + +customElements.define( + "rendered-html", + class RenderedHtml extends HTMLElement { + constructor() { + super() + this._content = "" + } + + set content(value) { + if (this._content === value) { + return + } + this._content = value + this.innerHTML = value + } + + get content() { + return this._content + } + } +)