├── LICENSE
├── README.md
├── container-queries
├── ad
│ ├── ad.css
│ ├── ad.js
│ └── index.html
├── css-element-queries
│ ├── LICENSE
│ ├── README.md
│ ├── index.js
│ ├── package.json
│ └── src
│ │ ├── ElementQueries.js
│ │ └── ResizeSensor.js
├── form
│ └── index.html
├── image
│ └── index.html
├── index.html
├── main.css
├── nav
│ └── index.html
├── product-items
│ └── index.html
└── third-party
│ ├── index.html
│ └── third-party.html
├── enhance-login-form
├── v1
│ └── index.html
├── v2
│ └── index.html
├── v3
│ ├── index.html
│ └── styles.css
├── v4
│ ├── index.html
│ └── styles.css
├── v5
│ ├── index.html
│ ├── script.js
│ └── styles.css
└── v6
│ ├── index.html
│ ├── script.js
│ └── styles.css
├── form-enhancement
├── style.css
├── v1
│ └── index.html
├── v2
│ └── index.html
├── v3
│ ├── enhanced.js
│ └── index.html
├── v4
│ ├── enhanced.js
│ └── index.html
├── v5
│ ├── enhanced.js
│ └── index.html
├── v6
│ ├── enhanced.js
│ └── index.html
└── v7
│ ├── enhanced.js
│ ├── idb-keyval.js
│ ├── index.html
│ └── service-worker.js
├── gifhancement
├── index.html
└── script.js
├── image-orientation
├── .gitignore
├── LICENSE
├── index.js
├── package-lock.json
├── package.json
├── public
│ └── css
│ │ └── main.css
├── uploads
│ └── notempty.txt
└── views
│ ├── index.pug
│ ├── layout.pug
│ └── show.pug
├── indicating-offline
├── article-1.html
├── article-2.html
├── article-3.html
├── article-4.html
├── article-5.html
├── bower_components
│ └── sw-toolbox
│ │ ├── .bower.json
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── bower.json
│ │ ├── docs
│ │ ├── cache-expiration-options
│ │ │ ├── app.js
│ │ │ ├── index.html
│ │ │ ├── service-worker.js
│ │ │ └── styles.css
│ │ ├── fonts
│ │ │ ├── OpenSans-Bold-webfont.eot
│ │ │ ├── OpenSans-Bold-webfont.svg
│ │ │ ├── OpenSans-Bold-webfont.woff
│ │ │ ├── OpenSans-BoldItalic-webfont.eot
│ │ │ ├── OpenSans-BoldItalic-webfont.svg
│ │ │ ├── OpenSans-BoldItalic-webfont.woff
│ │ │ ├── OpenSans-Italic-webfont.eot
│ │ │ ├── OpenSans-Italic-webfont.svg
│ │ │ ├── OpenSans-Italic-webfont.woff
│ │ │ ├── OpenSans-Light-webfont.eot
│ │ │ ├── OpenSans-Light-webfont.svg
│ │ │ ├── OpenSans-Light-webfont.woff
│ │ │ ├── OpenSans-LightItalic-webfont.eot
│ │ │ ├── OpenSans-LightItalic-webfont.svg
│ │ │ ├── OpenSans-LightItalic-webfont.woff
│ │ │ ├── OpenSans-Regular-webfont.eot
│ │ │ ├── OpenSans-Regular-webfont.svg
│ │ │ └── OpenSans-Regular-webfont.woff
│ │ ├── index.html
│ │ ├── scripts
│ │ │ ├── linenumber.js
│ │ │ └── prettify
│ │ │ │ ├── Apache-License-2.0.txt
│ │ │ │ ├── lang-css.js
│ │ │ │ └── prettify.js
│ │ ├── styles
│ │ │ ├── jsdoc-default.css
│ │ │ ├── prettify-jsdoc.css
│ │ │ └── prettify-tomorrow.css
│ │ ├── sw-toolbox.js
│ │ ├── tutorial-api.html
│ │ ├── tutorial-recipes.html
│ │ └── tutorial-usage.html
│ │ ├── package.json
│ │ ├── sw-toolbox.js
│ │ └── sw-toolbox.map.json
├── index.html
└── service-worker.js
├── link-to-button
├── a11y-dialog.js
├── index.html
├── login.html
├── script.js
└── style.css
├── network-based-img-loading
├── img
│ ├── test.jpg
│ ├── test_high.jpg
│ └── test_low.jpg
├── index.html
└── sw.js
├── observe-onresize-css-custom-properties
└── index.html
├── push-notifications
├── .env.example
├── .gitignore
├── README.md
├── controllers
│ ├── home.js
│ └── push.js
├── index.js
├── models
│ └── Push.js
├── package.json
├── public
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── css
│ │ └── main.css
│ ├── manifest.json
│ ├── script
│ │ └── push.js
│ └── sw.js
└── views
│ ├── home.pug
│ ├── layout.pug
│ └── partials
│ ├── footer.pug
│ └── header.pug
├── responsive-pop-out-menu
├── index.html
├── logo.svg
├── script.js
└── style.css
├── text-to-speech
└── index.html
├── theme-changer-css-custom-properties
└── index.html
├── toggle-content
├── jquery.js
└── showmore
│ ├── with-jquery
│ └── index.html
│ ├── with-js
│ └── index.html
│ └── with-target
│ └── index.html
├── web-share-api
└── using-the-web-share-api.html
└── web-share-target-image-to-grayscale
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── index.html
├── manifest.webmanifest
├── mstile-150x150.png
├── output.html
└── sw.js
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Michael Scharnagl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Demos
2 | ============
3 |
4 | Author: Michael Scharnagl [@justmarkup](https://twitter.com/justmarkup)
5 |
6 | Fixing image orientation on upload
7 | ------------
8 | * Article: https://justmarkup.com/articles/2019-10-21-image-orientation
9 |
10 | Transforming a link to a login page to a button opening the login form in an overlay
11 | ------------
12 | * Demo: http://justmarkup.github.io/demos/link-to-button
13 | * Article: https://justmarkup.com/log/2019/01/the-link-to-button-enhancement/
14 |
15 | Enhancing a login form – from basic to validation to reveal password
16 | ------------
17 | * Demo: https://enhance-login-form.glitch.me/login
18 | * Article: https://justmarkup.com/log/2018/02/enhancing-a-login-form/
19 |
20 | Network based image loading using the Network Information API in Service Worker
21 | ------------
22 | * Demo: http://justmarkup.github.io/demos/network-based-img-loading/
23 | * Article: https://justmarkup.com/log/2017/11/network-based-image-loading/
24 |
25 | Implementing push notifications on the front-end and back-end
26 | ------------
27 | * Demo: https://push-notifications-vwursywdxa.now.sh/
28 | * Article: https://justmarkup.com/log/2017/02/implementing-push-notifications/
29 |
30 | Observe states on resize using CSS custom properties
31 | ------------
32 | * Demo: https://justmarkup.com/demos/observe-onresize-css-custom-properties/
33 | * Article: https://justmarkup.com/log/2017/01/handling-states-on-resize-using-css-custom-properties
34 |
35 | Show More Pattern
36 | ------------
37 | * Demo: [Using :target](https://justmarkup.com/demos/toggle-content/showmore/with-target), [Using JavaScript](https://justmarkup.com/demos/toggle-content/showmore/with-jquery/) and [Using jQuery](https://justmarkup.com/demos/toggle-content/showmore/with-js/)
38 | * Article: https://justmarkup.com/log/2017/01/truncating-and-revealing-text-the-show-more-and-read-more-patterns/
39 |
40 | Enhancing a comment form
41 | ------------
42 | * Demo: http://justmarkup.github.io/demos/form-enhancement/v7/
43 | * Article: https://justmarkup.com/log/2016/10/enhancing-a-comment-form/
44 |
45 | Using the Web Share API
46 | ------------
47 | * Demo: https://justmarkup.com/web-share-api/using-the-web-share-api.html
48 | * Article: https://justmarkup.com/log/2016/10/using-the-web-share-api/
49 |
50 | Grey-out pages not in Cache
51 | ------------
52 | * Demo: https://justmarkup.github.io/demos/indicating-offline/
53 | * Article: https://justmarkup.com/log/2016/08/indicating-offline/
54 |
55 | Use cases for container queries
56 | ------------
57 | * Demo: https://justmarkup.com/demos/container-queries/
58 | * Article: https://justmarkup.com/log/2016/02/use-cases-for-container-queries/
59 |
60 | Theme changer using custom properties
61 | ------------
62 | * Demo: https://justmarkup.com/demos/custom-prop/
63 | * Article: https://justmarkup.com/log/2016/02/theme-switcher-using-css-custom-properties/
64 |
65 | Responsive Pop-out Menu
66 | ------------
67 | * Demo: https://justmarkup.com/demos/responsive-pop-out-menu
68 | * Article: https://justmarkup.com/log/2015/03/03/demo-responsive-pop-out-menu/
69 |
70 |
--------------------------------------------------------------------------------
/container-queries/ad/ad.css:
--------------------------------------------------------------------------------
1 | .ad {
2 | font-size: 1em;
3 | border: 1px dashed #ddd;
4 | padding: 0.4em;
5 | }
6 |
7 | .ad h3 {
8 | font-size: 1.4em;
9 | margin: 0;
10 | }
11 |
12 | .ad p {
13 | font-size: 1em;
14 | margin: 0.4em 0 0 0;
15 | }
16 |
17 | .ad a {
18 | text-decoration: none;
19 | color: #111;
20 | }
21 |
22 | .ad[min-width~="500px"] {
23 | font-size: 1.4em;
24 | }
25 |
--------------------------------------------------------------------------------
/container-queries/ad/ad.js:
--------------------------------------------------------------------------------
1 | // insert styles for the ad
2 | var link = document.createElement('link');
3 | link.rel = 'stylesheet';
4 | link.href = 'ad.css';
5 | document.head.appendChild(link);
6 |
7 | // get some random ad :-)
8 | var ads = [
9 | {
10 | headline: "The best ad ever",
11 | url: "https://justmarkup.com/log/",
12 | text: "Forget everything, this is the best ad ever, click click click"
13 | },
14 | {
15 | headline: "HTML5 rocks!",
16 | url: "https://justmarkup.com/log/",
17 | text: "I want Flash, I want Flash, I want Flash"
18 | }
19 | ];
20 |
21 | var divs = document.querySelectorAll('.ad');
22 |
23 | // iterate over each ad container and add random ad
24 | [].forEach.call(divs, function(div) {
25 | var ad = ads[Math.floor(Math.random() * ads.length)];
26 | div.innerHTML = '' + ad.headline + ' ' + ad.text + '
';
27 | });
28 |
--------------------------------------------------------------------------------
/container-queries/ad/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Advertisement || Container queries
7 |
8 |
11 |
12 |
13 |
16 |
17 |
18 |
22 |
23 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/container-queries/css-element-queries/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2013 Marc J. Schmidt
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in
11 | all copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/container-queries/css-element-queries/README.md:
--------------------------------------------------------------------------------
1 | # CSS Element Queries
2 |
3 |
4 | [](https://gitter.im/marcj/css-element-queries?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
5 |
6 | Element Queries is a polyfill adding support for element based media-queries to all new browsers (incl. IE7+).
7 | It allows not only to define media-queries based on window-size but also adds 'media-queries' functionality depending on element (any selector supported)
8 | size while not causing performance lags due to event based implementation.
9 |
10 | It's a proof-of-concept event-based CSS element dimension query with valid CSS selector syntax.
11 |
12 | Features:
13 |
14 | - no performance issues since it listens only on size changes of elements that have element query rules defined through css. Other element query polifills only listen on `window.onresize` which causes performance issues and allows only to detect changes via window.resize event and not inside layout changes like css3 animation, :hover, DOM changes etc.
15 | - no interval/timeout detection. Truly event-based through integrated ResizeSensor class.
16 | - no CSS modifications. Valid CSS Syntax
17 | - all CSS selectors available. Uses regular attribute selector. No need to write rules in HTML.
18 | - supports and tested in webkit, gecko and IE(7/8/9/10/11)
19 | - `min-width`, `min-height`, `max-width` and `max-height` are supported so far
20 | - works with any layout modifications: HTML (innerHTML etc), inline styles, DOM mutation, CSS3 transitions, fluid layout changes (also percent changes), pseudo classes (:hover etc.), window resizes and more
21 | - no Javascript-Framework dependency (works with jQuery, Mootools, etc.)
22 | - Works beautiful for responsive images without FOUC
23 |
24 | More demos and information: http://marcj.github.io/css-element-queries/
25 |
26 | ## Examples
27 |
28 | ### Element Query
29 |
30 | ```css
31 | .widget-name h2 {
32 | font-size: 12px;
33 | }
34 |
35 | .widget-name[min-width~="400px"] h2 {
36 | font-size: 18px;
37 | }
38 |
39 | .widget-name[min-width~="600px"] h2 {
40 | padding: 55px;
41 | text-align: center;
42 | font-size: 24px;
43 | }
44 |
45 | .widget-name[min-width~="700px"] h2 {
46 | font-size: 34px;
47 | color: red;
48 | }
49 | ```
50 |
51 | ```html
52 |
53 |
Element responsiveness FTW!
54 |
55 | ```
56 |
57 | ### Responsive image
58 |
59 | ```html
60 |
65 | ```
66 |
67 | Include the javascript files at the bottom and you're good to go. No custom javascript calls needed.
68 |
69 | ```html
70 |
71 |
72 | ```
73 |
74 | ## See it in action:
75 |
76 | Here live http://marcj.github.io/css-element-queries/.
77 |
78 | 
79 |
80 |
81 | ## Module Loader
82 |
83 | If you're using a module loader you need to trigger the event listening or initialization yourself:
84 |
85 | ```javascript
86 | var EQ = require('node_modules/css-element-queries/ElementQueries');
87 |
88 | //attaches to DOMLoadContent
89 | EQ.listen();
90 |
91 | //or if you want to trigger it yourself.
92 | // Parse all available CSS and attach ResizeSensor to those elements which have rules attached
93 | // (make sure this is called after 'load' event, because CSS files are not ready when domReady is fired.
94 | EQ.init();
95 | ```
96 |
97 | ## Issues
98 |
99 | - So far does not work on `img` and other elements that can't contain other elements. Wrapping with a `div` works fine though (See demo).
100 | - Adds additional hidden elements into selected target element and forces target element to be relative or absolute.
101 |
102 |
103 | ## License
104 |
105 | MIT license. Copyright [Marc J. Schmidt](https://twitter.com/MarcJSchmidt).
106 |
--------------------------------------------------------------------------------
/container-queries/css-element-queries/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ResizeSensor: require('./src/ResizeSensor'),
3 | ElementQueries: require('./src/ElementQueries')
4 | };
5 |
--------------------------------------------------------------------------------
/container-queries/css-element-queries/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-element-queries",
3 | "version": "0.3.2",
4 | "description": "CSS-Element-Queries Polyfill. proof-of-concept for high-speed element dimension/media queries in valid css.",
5 | "main": "index.js",
6 | "directories": {
7 | "test": "test"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git@github.com:marcj/css-element-queries.git"
15 | },
16 | "author": "Marc J. Schmidt",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/marcj/css-element-queries/issues"
20 | },
21 | "homepage": "https://github.com/marcj/css-element-queries",
22 | "devDependencies": {
23 | "grunt": "^0.4.5",
24 | "grunt-bump": "^0.3.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/container-queries/form/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Login form || Container queries
7 |
8 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
Login
24 |
32 |
33 |
34 |
45 |
46 |
47 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/container-queries/image/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Images || Container queries
7 |
8 |
11 |
12 |
13 |
16 |
17 |
18 |
26 |
27 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/container-queries/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Use cases for container queries
7 |
8 |
24 |
25 |
26 |
27 | Use cases for container queries
28 |
29 |
30 | Demos & code
31 |
39 |
40 |
41 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/container-queries/main.css:
--------------------------------------------------------------------------------
1 |
2 | /* main styles */
3 | html,
4 | body {
5 | margin: 0;
6 | font-family: Helvetica, Arial, sans-serif;
7 | }
8 |
9 | img {
10 | max-width: 100%;
11 | height: auto;
12 | }
13 |
14 | header {
15 | background: #303F9F;
16 | color: #fff;
17 | box-shadow: 0 2px 6px rgba(0,0,0,0.2);
18 | }
19 |
20 | .clearfix:after {
21 | content: "";
22 | display: block;
23 | clear: both;
24 | }
25 |
26 | header h1 {
27 | margin: 0;
28 | padding: 20px;
29 | font-weight: normal;
30 | float: left;
31 | }
32 |
33 | nav ul {
34 | margin: 0;
35 | padding: 0;
36 | list-style: none;
37 | }
38 |
39 | nav a {
40 | display: block;
41 | float: left;
42 | padding: 26px 20px;
43 | color: #fff;
44 | font-size: 1.4em;
45 | text-decoration: none;
46 | }
47 |
48 | h2,
49 | h3 {
50 | font-weight: normal;
51 | }
52 |
53 | h3 a {
54 | color: #111;
55 | }
56 |
57 | main {
58 | width: 1200px;
59 | max-width: 90%;
60 | max-width: calc(100% - 40px);
61 | margin: 0 auto;
62 | min-height: 100vh;
63 | }
64 |
65 | aside {
66 | min-height: 100vh;
67 | }
68 |
69 | @media all and (min-width: 1200px) {
70 | main .alpha {
71 | float: left;
72 | width: 72%;
73 | }
74 | aside {
75 | float: right;
76 | width: 25%;
77 | }
78 | }
79 |
80 | footer {
81 | clear: both;
82 | border-top: 1px solid #B6B6B6;
83 | padding: 20px;
84 | }
85 |
--------------------------------------------------------------------------------
/container-queries/nav/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Navigation || Container queries
7 |
8 |
18 |
19 |
20 |
21 | My blog
22 |
23 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
44 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/container-queries/product-items/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Product items || Container queries
7 |
8 |
54 |
55 |
56 |
59 |
60 |
61 |
62 |
Popular products
63 |
64 |
65 |
66 |
67 |
68 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dignissimos itaque optio a labore laudantium eius pariatur eaque reiciendis maxime maiores.
69 |
70 |
71 |
72 |
73 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dignissimos itaque optio a labore laudantium eius pariatur eaque reiciendis maxime maiores.
74 |
75 |
76 |
77 |
78 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dignissimos itaque optio a labore laudantium eius pariatur eaque reiciendis maxime maiores, est laboriosam, nihil sed rerum, perspiciatis sunt non similique minus.
79 |
80 |
81 |
82 |
83 |
84 |
85 | Newest product
86 |
95 |
96 |
97 |
98 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/container-queries/third-party/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Third-party iframe || Container queries
7 |
8 |
15 |
16 |
17 |
20 |
21 |
22 |
23 |
Third-party widget
24 |
25 |
26 |
27 |
28 | Third-party widget
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/container-queries/third-party/third-party.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Third-party Widget
6 |
21 |
22 |
23 | I am a third-party widget
24 |
25 |
26 | If my container is 300px or less I am red, otherwise I am blue
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/enhance-login-form/v1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Part One | Enhancing a login form
9 |
10 |
11 |
12 | Login
13 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/enhance-login-form/v2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Part Two | Enhancing a login form
9 |
10 |
11 |
12 | Login
13 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/enhance-login-form/v3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Part Three | Enhancing a login form
9 |
10 |
11 |
12 |
13 | Login
14 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/enhance-login-form/v3/styles.css:
--------------------------------------------------------------------------------
1 | /* General styles */
2 |
3 | html {
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
6 | color: #111;
7 | box-sizing: border-box;
8 | }
9 |
10 | *,
11 | *:before,
12 | *:after {
13 | box-sizing: inherit;
14 | }
15 |
16 | body {
17 | max-width: 600px;
18 | margin: 0 auto;
19 | padding: 0 20px;
20 | }
21 |
22 | form:after {
23 | content: "";
24 | display: table;
25 | clear: both;
26 | }
27 |
28 | a {
29 | color: #0a39ce;
30 | }
31 |
32 |
33 | /* login form styles */
34 |
35 | .form__element {
36 | margin: 0 0 1em 0;
37 | }
38 |
39 | .form__label {
40 | font-size: 1.2em;
41 | display: block;
42 | margin: 0 0 0.4em 0;
43 | }
44 |
45 | .form__input {
46 | width: 100%;
47 | padding: 0.6em;
48 | font-size: 1.3em;
49 | }
50 |
51 | .form__required {
52 | float: right;
53 | font-size: 0.8em;
54 | background: #ddd;
55 | padding: 0.3em;
56 | }
57 |
58 | .form__hint {
59 | margin: 0;
60 | font-size: 1.1em;
61 | }
62 |
63 | .form__hint:before {
64 | content: "\1F6C8";
65 | font-size: 1.2em;
66 | margin: 0 0.2em 0 0;
67 | }
68 |
69 | .form__submit {
70 | background: #326f10;
71 | color: #fff;
72 | border: none;
73 | padding: 0.6em;
74 | font-size: 1.4em;
75 | float: right;
76 | }
--------------------------------------------------------------------------------
/enhance-login-form/v4/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Part Four | Enhancing a login form
9 |
10 |
11 |
12 |
13 | Login
14 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/enhance-login-form/v4/styles.css:
--------------------------------------------------------------------------------
1 | /* General styles */
2 |
3 | html {
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
6 | color: #111;
7 | box-sizing: border-box;
8 | }
9 |
10 | *,
11 | *:before,
12 | *:after {
13 | box-sizing: inherit;
14 | }
15 |
16 | body {
17 | max-width: 600px;
18 | margin: 0 auto;
19 | padding: 0 20px;
20 | }
21 |
22 | form:after {
23 | content: "";
24 | display: table;
25 | clear: both;
26 | }
27 |
28 | a {
29 | color: #0a39ce;
30 | }
31 |
32 |
33 | /* login form styles */
34 |
35 | .form__element {
36 | margin: 0 0 1em 0;
37 | }
38 |
39 | .form__label {
40 | font-size: 1.2em;
41 | display: block;
42 | margin: 0 0 0.4em 0;
43 | }
44 |
45 | .form__input {
46 | width: 100%;
47 | padding: 0.6em;
48 | font-size: 1.3em;
49 | border: 2px solid ##eee;
50 | }
51 |
52 | .form__required {
53 | float: right;
54 | font-size: 0.8em;
55 | background: #ddd;
56 | padding: 0.3em;
57 | }
58 |
59 | .form__hint {
60 | margin: 0;
61 | font-size: 1.1em;
62 | }
63 |
64 | .form__hint:before {
65 | content: "\1F6C8";
66 | font-size: 1.2em;
67 | margin: 0 0.2em 0 0;
68 | }
69 |
70 | .form__submit {
71 | background: #326f10;
72 | color: #fff;
73 | border: none;
74 | padding: 0.6em;
75 | font-size: 1.4em;
76 | float: right;
77 | }
78 |
79 | .form__error {
80 | margin: 0;
81 | padding: 0.4em;
82 | color: #b0141d;
83 | background-color: #fbd8d8;
84 | }
85 |
86 | .has-error input {
87 | border: 2px solid #b0141d;
88 | }
--------------------------------------------------------------------------------
/enhance-login-form/v5/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Part Five | Enhancing a login form
9 |
10 |
11 |
12 |
13 | Login
14 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/enhance-login-form/v5/script.js:
--------------------------------------------------------------------------------
1 | // cut the mustard
2 | var form = document.createElement('form');
3 | if ('checkValidity' in form && 'querySelector' in document && 'classList' in document.documentElement) {
4 |
5 | // feedback messages
6 | var messageComponents = document.querySelectorAll("[data-message]");
7 |
8 | if (messageComponents.length > 0) {
9 |
10 | [].forEach.call(messageComponents, function(message) {
11 | var messageButton = message.querySelector("[data-close-notification]");
12 |
13 | messageButton.removeAttribute('hidden');
14 |
15 | messageButton.addEventListener("click", function() {
16 | this.parentElement.hidden = true;
17 | });
18 | });
19 | }
20 |
21 | // form validation
22 | var inputs = document.querySelectorAll("[data-error]");
23 |
24 | if (inputs.length > 0) {
25 |
26 | var toggleErrorMessage = function(input, hasError) {
27 | var message = (input.value === '') ? input.dataset.empty : input.dataset.error;
28 | var oldMessage = document.getElementById("alert-" + input.name);
29 | var newMessage;
30 |
31 | if (hasError) {
32 | if (!oldMessage) {
33 | newMessage = document.createElement("p");
34 | newMessage.setAttribute('role', 'alert');
35 | newMessage.classList.add('form__error');
36 | newMessage.setAttribute('id', 'alert-' + input.name);
37 | } else {
38 | newMessage = oldMessage;
39 | }
40 |
41 | newMessage.innerText = message;
42 |
43 | input.setAttribute('aria-describedby', 'alert-' + input.name);
44 | input.parentElement.appendChild(newMessage);
45 |
46 | input.parentElement.classList.add('has-error');
47 | } else {
48 |
49 | if (oldMessage) {
50 | input.parentElement.removeChild(oldMessage);
51 | input.removeAttribute('aria-describedby');
52 | input.parentElement.classList.remove('has-error');
53 | }
54 |
55 | }
56 | };
57 | // loop over each input
58 | [].forEach.call(inputs, function(input) {
59 |
60 | // check validation on blur
61 | input.addEventListener("blur", function(event) {
62 | input.checkValidity();
63 |
64 | if (input.checkValidity()) {
65 | input.classList.remove("error");
66 | input.setAttribute("aria-invalid", "false");
67 | toggleErrorMessage(input, false);
68 | } else {
69 | input.classList.add("error");
70 | input.setAttribute("aria-invalid", "true");
71 | toggleErrorMessage(input, true);
72 | }
73 | });
74 | });
75 | }
76 | }
--------------------------------------------------------------------------------
/enhance-login-form/v5/styles.css:
--------------------------------------------------------------------------------
1 | /* General styles */
2 |
3 | html {
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
6 | color: #111;
7 | box-sizing: border-box;
8 | }
9 |
10 | *,
11 | *:before,
12 | *:after {
13 | box-sizing: inherit;
14 | }
15 |
16 | body {
17 | max-width: 600px;
18 | margin: 0 auto;
19 | padding: 0 20px;
20 | }
21 |
22 | form:after {
23 | content: "";
24 | display: table;
25 | clear: both;
26 | }
27 |
28 | a {
29 | color: #0a39ce;
30 | }
31 |
32 |
33 | /* login form styles */
34 |
35 | .form__element {
36 | margin: 0 0 1em 0;
37 | }
38 |
39 | .form__label {
40 | font-size: 1.2em;
41 | display: block;
42 | margin: 0 0 0.4em 0;
43 | }
44 |
45 | .form__input {
46 | width: 100%;
47 | padding: 0.6em;
48 | font-size: 1.3em;
49 | border: 2px solid ##eee;
50 | }
51 |
52 | .form__required {
53 | float: right;
54 | font-size: 0.8em;
55 | background: #ddd;
56 | padding: 0.3em;
57 | }
58 |
59 | .form__hint {
60 | margin: 0;
61 | font-size: 1.1em;
62 | }
63 |
64 | .form__hint:before {
65 | content: "\1F6C8";
66 | font-size: 1.2em;
67 | margin: 0 0.2em 0 0;
68 | }
69 |
70 | .form__submit {
71 | background: #326f10;
72 | color: #fff;
73 | border: none;
74 | padding: 0.6em;
75 | font-size: 1.4em;
76 | float: right;
77 | }
78 |
79 | .form__error {
80 | margin: 0;
81 | padding: 0.4em;
82 | color: #b0141d;
83 | background-color: #fbd8d8;
84 | }
85 |
86 | .has-error input {
87 | border: 2px solid #b0141d;
88 | }
--------------------------------------------------------------------------------
/enhance-login-form/v6/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Part Six | Enhancing a login form
9 |
10 |
11 |
12 |
13 | Login
14 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/enhance-login-form/v6/script.js:
--------------------------------------------------------------------------------
1 | // cut the mustard
2 | var form = document.createElement('form');
3 | if ('checkValidity' in form && 'querySelector' in document && 'classList' in document.documentElement) {
4 |
5 | // feedback messages
6 | var messageComponents = document.querySelectorAll("[data-message]");
7 |
8 | if (messageComponents.length > 0) {
9 |
10 | [].forEach.call(messageComponents, function(message) {
11 | var messageButton = message.querySelector("[data-close-notification]");
12 |
13 | messageButton.removeAttribute('hidden');
14 |
15 | messageButton.addEventListener("click", function() {
16 | this.parentElement.hidden = true;
17 | });
18 | });
19 | }
20 |
21 | // form validation
22 | var inputs = document.querySelectorAll("[data-error]");
23 |
24 | if (inputs.length > 0) {
25 |
26 | var toggleErrorMessage = function(input, hasError) {
27 | var message = (input.value === '') ? input.dataset.empty : input.dataset.error;
28 | var oldMessage = document.getElementById("alert-" + input.name);
29 | var newMessage;
30 |
31 | if (hasError) {
32 | if (!oldMessage) {
33 | newMessage = document.createElement("p");
34 | newMessage.setAttribute('role', 'alert');
35 | newMessage.classList.add('form__error');
36 | newMessage.setAttribute('id', 'alert-' + input.name);
37 | } else {
38 | newMessage = oldMessage;
39 | }
40 |
41 |
42 | newMessage.innerText = message;
43 |
44 | input.setAttribute('aria-describedby', 'alert-' + input.name);
45 | input.parentElement.appendChild(newMessage);
46 |
47 | input.parentElement.classList.add('has-error');
48 | } else {
49 |
50 | if (oldMessage) {
51 | input.parentElement.removeChild(oldMessage);
52 | input.removeAttribute('aria-describedby');
53 | input.parentElement.classList.remove('has-error');
54 | }
55 |
56 | }
57 | };
58 |
59 | [].forEach.call(inputs, function(input) {
60 |
61 | input.addEventListener("blur", function(event) {
62 |
63 | if (event.relatedTarget && event.relatedTarget.nodeName === 'BUTTON') {
64 | return;
65 | }
66 |
67 | input.checkValidity();
68 |
69 | if (input.checkValidity()) {
70 | input.classList.remove("error");
71 | input.setAttribute("aria-invalid", "false");
72 | toggleErrorMessage(input, false);
73 | } else {
74 | input.classList.add("error");
75 | input.setAttribute("aria-invalid", "true");
76 | toggleErrorMessage(input, true);
77 | }
78 | });
79 | });
80 | }
81 |
82 | // toggle password field between type="text" and type="password"
83 | var togglePasswordButton = document.querySelector('[data-toggle-password]');
84 |
85 | if (togglePasswordButton) {
86 | var togglePasswordButtonText = togglePasswordButton.querySelector('span');
87 | var passwordField = document.querySelector('[data-toggle-password-field]');
88 | togglePasswordButton.removeAttribute('hidden');
89 |
90 |
91 | togglePasswordButton.addEventListener('click', function() {
92 | var isPressed = JSON.parse(this.getAttribute('aria-pressed'));
93 |
94 | if (isPressed) {
95 | passwordField.setAttribute('type', 'password');
96 | this.setAttribute('aria-pressed', false);
97 | togglePasswordButtonText.textContent = 'Show password';
98 | } else {
99 | passwordField.setAttribute('type', 'text');
100 | this.setAttribute('aria-pressed', true);
101 | togglePasswordButtonText.textContent = 'Hide password';
102 | }
103 | });
104 | }
105 | }
--------------------------------------------------------------------------------
/enhance-login-form/v6/styles.css:
--------------------------------------------------------------------------------
1 | /* General styles */
2 |
3 | html {
4 | margin: 0;
5 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
6 | color: #111;
7 | box-sizing: border-box;
8 | }
9 |
10 | *,
11 | *:before,
12 | *:after {
13 | box-sizing: inherit;
14 | }
15 |
16 | body {
17 | max-width: 600px;
18 | margin: 0 auto;
19 | padding: 0 20px;
20 | }
21 |
22 | form:after {
23 | content: "";
24 | display: table;
25 | clear: both;
26 | }
27 |
28 | a {
29 | color: #0a39ce;
30 | }
31 |
32 |
33 | /* login form styles */
34 |
35 | .form__element {
36 | margin: 0 0 1em 0;
37 | }
38 |
39 | .form__element-inner {
40 | position: relative;
41 | }
42 |
43 | .form__label {
44 | font-size: 1.2em;
45 | display: block;
46 | margin: 0 0 0.4em 0;
47 | }
48 |
49 | .form__input {
50 | width: 100%;
51 | padding: 0.6em;
52 | font-size: 1.3em;
53 | border: 2px solid ##eee;
54 | }
55 |
56 | .form__required {
57 | float: right;
58 | font-size: 0.8em;
59 | background: #ddd;
60 | padding: 0.3em;
61 | }
62 |
63 | .form__hint {
64 | margin: 0;
65 | font-size: 1.1em;
66 | }
67 |
68 | .form__hint:before {
69 | content: "\1F6C8";
70 | font-size: 1.2em;
71 | margin: 0 0.2em 0 0;
72 | }
73 |
74 | .form__submit {
75 | background: #326f10;
76 | color: #fff;
77 | border: none;
78 | padding: 0.6em;
79 | font-size: 1.4em;
80 | float: right;
81 | }
82 |
83 | .form__error {
84 | margin: 0;
85 | padding: 0.4em;
86 | color: #b0141d;
87 | background-color: #fbd8d8;
88 | }
89 |
90 | .has-error input {
91 | border: 2px solid #b0141d;
92 | }
93 |
94 | .form__toggle-password {
95 | position: absolute;
96 | top: 3px;
97 | right: 1px;
98 | padding: 1.2em;
99 | background: #eee;
100 | border: none;
101 | }
--------------------------------------------------------------------------------
/form-enhancement/style.css:
--------------------------------------------------------------------------------
1 | html {
2 | font-family: Helvetica, Arial, sans-serif;
3 | }
4 | * {
5 | box-sizing: border-box;
6 | }
7 | body {
8 | width: 600px;
9 | max-width: 90%;
10 | margin: 0 auto;
11 | }
12 | label {
13 | display: block;
14 | padding: 0.8em 0 0.2em 0;
15 | }
16 | button {
17 | display: block;
18 | float: right;
19 | font-size: 16px;
20 | }
21 | textarea,
22 | input {
23 | width: 100%;
24 | width: calc(100% - 1.2em);
25 | padding: 0.6em;
26 | border: 1px solid #444;
27 | box-sizing: content-box;
28 | font-size: 16px;
29 |
30 | }
31 | textarea {
32 | height: 100px;
33 | background: transparent !important;
34 | }
35 | .error {
36 | color: red;
37 | }
38 | .info {
39 | color: orange;
40 | }
41 | .success {
42 | color: green;
43 | }
44 | .note {
45 | border: 1px solid #444;
46 | border-width: 0 1px 1px 1px;
47 | margin: 0;
48 | padding: 0.4em;
49 | background: #ddd;
50 | }
51 | .comments h4 {
52 | margin: 0.3em 0 0 0;
53 | }
54 | .comments p {
55 | margin: 0.4em 0 0 0;
56 | }
57 | .comments li {
58 | margin-bottom: 2em;
59 | }
60 | @supports (counter-reset: test) {
61 | .comments {
62 | counter-reset: comment;
63 | list-style-type: none;
64 | }
65 | .comments li {position: relative;}
66 | .comments li:before {
67 | counter-increment: comment;
68 | content: counters(comment,".") " ";
69 | font-size: 16px;
70 | position: absolute;
71 | left: -40px;
72 | top: 0;
73 | background: #ddd;
74 | border-radius: 50%;
75 | width: 30px;
76 | height: 30px;
77 | text-align: center;
78 | line-height: 30px;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/form-enhancement/v1/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo V1: Enhancing an form: From basic to custom error message to BackgroundSync
7 |
8 |
9 |
10 |
11 | Note: The comments are not saved in a database on submit. This example demonstrates the front-end but not the back-end.
12 |
13 | Comments
14 |
15 |
21 |
22 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/form-enhancement/v2/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo V1: Enhancing an form: From basic to custom error message to BackgroundSync
7 |
8 |
9 |
10 |
11 | Note: The comments are not saved in a database on submit. This example demonstrates the front-end but not the back-end.
12 |
13 | Comments
14 |
15 |
21 |
22 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/form-enhancement/v3/enhanced.js:
--------------------------------------------------------------------------------
1 | // set a custom text for the error message
2 | var commentArea = document.querySelector("#comment");
3 |
4 | commentArea.addEventListener('invalid', function (e) {
5 | e.target.setCustomValidity("");
6 | if (!e.target.validity.valid) {
7 | e.target.setCustomValidity("Please enter a comment.");
8 | }
9 | });
10 |
11 | commentArea.addEventListener('input', function (e) {
12 | e.target.setCustomValidity("");
13 | });
14 |
--------------------------------------------------------------------------------
/form-enhancement/v3/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo V3: Enhancing an form: From basic to custom error message to BackgroundSync
7 |
8 |
67 |
68 |
69 |
70 | Note: The comments are not saved in a database on submit. This example demonstrates the front-end but not the back-end.
71 |
72 | Comments
73 |
74 |
80 |
81 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/form-enhancement/v4/enhanced.js:
--------------------------------------------------------------------------------
1 | // set a custom text for the error message
2 | var commentArea = document.querySelector("#comment");
3 | var nameInput = document.querySelector("#name");
4 | var form = document.querySelector('form');
5 | var commentValue, nameValue;
6 |
7 | commentArea.addEventListener('invalid', function (e) {
8 | e.target.setCustomValidity("");
9 | if (!e.target.validity.valid) {
10 | e.target.setCustomValidity("Please enter a comment.");
11 | }
12 | });
13 |
14 | commentArea.addEventListener('input', function (e) {
15 | e.target.setCustomValidity("");
16 | });
17 |
18 |
19 | // send form data with JavaScript
20 | if( window.FormData) {
21 |
22 | var appendComment = function (nameValue, commentValue) {
23 | var comment = document.createElement('li');
24 | var commentName = document.createElement('h4');
25 | var commentComment = document.createElement('p');
26 | var commentWrapper = document.querySelector('.comments');
27 | commentName.innerText = nameValue;
28 | commentComment.innerText = commentValue;
29 | nameValue ? comment.appendChild(commentName) : '';
30 | comment.appendChild(commentComment);
31 | commentWrapper.appendChild(comment);
32 | };
33 |
34 | form.addEventListener('submit', function (ev) {
35 | var formData = new FormData(form);
36 | commentValue = commentArea.value;
37 | nameValue = nameInput.value;
38 |
39 | var xhr = new XMLHttpRequest();
40 | // save the comment in the database
41 | xhr.open('POST', './save', true);
42 | xhr.onload = function () {
43 | appendComment(nameValue, commentValue);
44 | };
45 | xhr.send(formData);
46 |
47 | // always call preventDefault at the end, see: http://molily.de/javascript-failure/
48 | ev.preventDefault();
49 | });
50 | }
51 |
52 |
53 |
--------------------------------------------------------------------------------
/form-enhancement/v4/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo V4: Enhancing an form: From basic to custom error message to BackgroundSync
7 |
8 |
67 |
68 |
69 |
70 | Note: The comments are not saved in a database on submit. This example demonstrates the front-end but not the back-end.
71 |
72 | Comments
73 |
74 |
80 |
81 |
90 |
91 |
92 |
93 |
--------------------------------------------------------------------------------
/form-enhancement/v5/enhanced.js:
--------------------------------------------------------------------------------
1 | // set a custom text for the error message
2 | var commentArea = document.querySelector("#comment");
3 | var nameInput = document.querySelector("#name");
4 | var form = document.querySelector('form');
5 | var commentValue, nameValue;
6 |
7 | commentArea.addEventListener('invalid', function (e) {
8 | e.target.setCustomValidity("");
9 | if (!e.target.validity.valid) {
10 | e.target.setCustomValidity("Please enter a comment.");
11 | }
12 | });
13 |
14 | commentArea.addEventListener('input', function (e) {
15 | e.target.setCustomValidity("");
16 | });
17 |
18 |
19 | // send form data with JavaScript
20 | if( window.FormData) {
21 |
22 | var appendComment = function (nameValue, commentValue) {
23 | var comment = document.createElement('li');
24 | var commentName = document.createElement('h4');
25 | var commentComment = document.createElement('p');
26 | var commentWrapper = document.querySelector('.comments');
27 | commentName.innerText = nameValue;
28 | commentComment.innerText = commentValue;
29 | nameValue ? comment.appendChild(commentName) : '';
30 | comment.appendChild(commentComment);
31 | commentWrapper.appendChild(comment);
32 | };
33 |
34 | form.addEventListener('submit', function (ev) {
35 | var formData = new FormData(form);
36 | commentValue = commentArea.value;
37 | nameValue = nameInput.value;
38 |
39 | var xhr = new XMLHttpRequest();
40 | // save the comment in the database
41 | xhr.open('POST', './save', true);
42 | xhr.onload = function () {
43 | appendComment(nameValue, commentValue);
44 | };
45 | xhr.send(formData);
46 |
47 | // always call preventDefault at the end, see: http://molily.de/javascript-failure/
48 | ev.preventDefault();
49 | });
50 | }
51 |
52 | // auto-expand the textarea
53 | commentArea.addEventListener('keydown', autosize);
54 |
55 | function autosize(){
56 | var el = this;
57 | setTimeout(function(){
58 | el.style.cssText = 'height:auto;';
59 | el.style.cssText = 'height:' + el.scrollHeight + 'px';
60 | },0);
61 | }
62 |
--------------------------------------------------------------------------------
/form-enhancement/v5/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo V5: Enhancing an form: From basic to custom error message to BackgroundSync
7 |
8 |
67 |
68 |
69 |
70 | Note: The comments are not saved in a database on submit. This example demonstrates the front-end but not the back-end.
71 |
72 | Comments
73 |
74 |
80 |
81 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/form-enhancement/v6/enhanced.js:
--------------------------------------------------------------------------------
1 | // set a custom text for the error message
2 | var commentArea = document.querySelector("#comment");
3 | var nameInput = document.querySelector("#name");
4 | var form = document.querySelector('form');
5 | var messageElement = document.querySelector('#feedback');
6 | var progressWidth;
7 |
8 | commentArea.addEventListener('invalid', function (e) {
9 | e.target.setCustomValidity("");
10 | if (!e.target.validity.valid) {
11 | e.target.setCustomValidity("Please enter a comment.");
12 | }
13 | });
14 |
15 | commentArea.addEventListener('input', function (e) {
16 | e.target.setCustomValidity("");
17 | });
18 |
19 |
20 | // send form data with JavaScript
21 | if( window.FormData) {
22 |
23 | var appendComment = function (nameValue, commentValue) {
24 | var comment = document.createElement('li');
25 | var commentName = document.createElement('h4');
26 | var commentComment = document.createElement('p');
27 | var commentWrapper = document.querySelector('.comments');
28 | commentName.innerText = nameValue;
29 | commentComment.innerText = commentValue;
30 | nameValue ? comment.appendChild(commentName) : '';
31 | comment.appendChild(commentComment);
32 | commentWrapper.appendChild(comment);
33 | };
34 |
35 | form.addEventListener('submit', function (ev) {
36 | var formData = new FormData(form);
37 | commentValue = commentArea.value;
38 | nameValue = nameInput.value;
39 |
40 | var xhr = new XMLHttpRequest();
41 | // save the comment in the database
42 | xhr.open('POST', './save', true);
43 | xhr.onload = function () {
44 | appendComment(nameValue, commentValue);
45 | };
46 | xhr.onerror = function (error) {
47 | messageElement.className = 'message error';
48 | messageElement.textContent = 'There was an error posting the comment. Please try again.';
49 | };
50 | xhr.upload.onprogress = function (evt) {
51 | messageElement.textContent = 'Uploading: ' + evt.loaded/evt.total*100;
52 | };
53 | xhr.upload.onloadend = function (evt) {
54 | messageElement.className = 'message success';
55 | messageElement.textContent = 'Your comment was posted sucessfully.';
56 | };
57 | xhr.send(formData);
58 |
59 | // always call preventDefault at the end, see: http://molily.de/javascript-failure/
60 | ev.preventDefault();
61 | });
62 | }
63 |
64 |
65 | // auto-expand the textarea
66 | commentArea.addEventListener('keydown', autosize);
67 |
68 | function autosize(){
69 | var el = this;
70 | setTimeout(function(){
71 | el.style.cssText = 'height:auto;';
72 | el.style.cssText = 'height:' + el.scrollHeight + 'px';
73 | },0);
74 | }
75 |
--------------------------------------------------------------------------------
/form-enhancement/v6/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo V6: Enhancing an form: From basic to custom error message to BackgroundSync
7 |
8 |
67 |
68 |
69 |
70 | Note: The comments are not saved in a database on submit. This example demonstrates the front-end but not the back-end.
71 |
72 | Comments
73 |
74 |
80 |
81 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/form-enhancement/v7/enhanced.js:
--------------------------------------------------------------------------------
1 | // idb-keyval: https://github.com/jakearchibald/idb-keyval
2 | !function(){"use strict";function e(){return t||(t=new Promise(function(e,n){var t=indexedDB.open("keyval-store",1);t.onerror=function(){n(t.error)},t.onupgradeneeded=function(){t.result.createObjectStore("keyval")},t.onsuccess=function(){e(t.result)}})),t}function n(n,t){return e().then(function(e){return new Promise(function(r,o){var u=e.transaction("keyval",n);u.oncomplete=function(){r()},u.onerror=function(){o(u.error)},t(u.objectStore("keyval"))})})}var t,r={get:function(e){var t;return n("readonly",function(n){t=n.get(e)}).then(function(){return t.result})},set:function(e,t){return n("readwrite",function(n){n.put(t,e)})},delete:function(e){return n("readwrite",function(n){n.delete(e)})},clear:function(){return n("readwrite",function(e){e.clear()})},keys:function(){var e=[];return n("readonly",function(n){(n.openKeyCursor||n.openCursor).call(n).onsuccess=function(){this.result&&(e.push(this.result.key),this.result.continue())}}).then(function(){return e})}};"undefined"!=typeof module&&module.exports?module.exports=r:self.idbKeyval=r}();
3 |
4 | var commentArea = document.querySelector("#comment");
5 | var nameInput = document.querySelector("#name");
6 | var form = document.querySelector('form');
7 | var messageElement = document.querySelector('#feedback');
8 | var progressWidth;
9 |
10 | // set a custom text for the error message
11 | commentArea.addEventListener('invalid', function (e) {
12 | e.target.setCustomValidity("");
13 | if (!e.target.validity.valid) {
14 | e.target.setCustomValidity("Please enter a comment.");
15 | }
16 | });
17 |
18 | commentArea.addEventListener('input', function (e) {
19 | e.target.setCustomValidity("");
20 | });
21 |
22 | // helper function to append comment to existing comments
23 | var appendComment = function (nameValue, commentValue) {
24 | var comment = document.createElement('li');
25 | var commentName = document.createElement('h4');
26 | var commentComment = document.createElement('p');
27 | var commentWrapper = document.querySelector('.comments');
28 | commentName.innerText = nameValue;
29 | commentComment.innerText = commentValue;
30 | nameValue ? comment.appendChild(commentName) : '';
31 | comment.appendChild(commentComment);
32 | commentWrapper.appendChild(comment);
33 | };
34 |
35 | // check for service worker support
36 | if ('serviceWorker' in navigator) {
37 | // register the service worker
38 | navigator.serviceWorker.register('./service-worker.js');
39 |
40 | form.addEventListener('submit', function (ev) {
41 |
42 | let formData = new FormData(form);
43 | // send message via BackgroundSync
44 | navigator.serviceWorker.ready.then(function(swRegistration) {
45 | console.log('service worker ready');
46 |
47 | idbKeyval.set('comment', commentArea.value);
48 | idbKeyval.set('name', nameInput.value ? nameInput.value : false);
49 | messageElement.className = 'message info';
50 | messageElement.textContent = 'It seems you are offline. Comment will be published automatically once you are online again.';
51 |
52 | return swRegistration.sync.register('form-post');
53 | });
54 |
55 | // always call preventDefault at the end, see: http://molily.de/javascript-failure/
56 | ev.preventDefault();
57 | });
58 |
59 | // event to receive messages send by service worker
60 | navigator.serviceWorker.addEventListener('message', function(event){
61 | if (event.data == 'success') {
62 | messageElement.className = 'message success';
63 | messageElement.textContent = 'Your comment was posted sucessfully.';
64 | let nameValue = false;
65 | idbKeyval.get('name').then(function (data) {
66 | nameValue = data;
67 | let commentValue = '';
68 | idbKeyval.get('comment').then(function (data) {
69 | commentValue = data;
70 | appendComment(nameValue, commentValue);
71 | });
72 | });
73 |
74 | } else if (event.data == 'error') {
75 | messageElement.className = 'message error';
76 | messageElement.textContent = 'There was an error posting the comment. Please try again later.';
77 | }
78 | });
79 |
80 |
81 | } else if ( window.FormData) {
82 |
83 | form.addEventListener('submit', function (ev) {
84 | var formData = new FormData(form);
85 | commentValue = commentArea.value;
86 | nameValue = nameInput.value;
87 |
88 | var xhr = new XMLHttpRequest();
89 | // save the comment in the database
90 | xhr.open('POST', './save', true);
91 | xhr.onload = function () {
92 | appendComment(nameValue, commentValue);
93 | };
94 | xhr.onerror = function (error) {
95 | messageElement.className = 'message error';
96 | messageElement.textContent = 'There was an error posting the comment. Please try again later.';
97 | };
98 | xhr.upload.onprogress = function (evt) {
99 | messageElement.className = 'message info';
100 | messageElement.textContent = 'Uploading: ' + parseInt(evt.loaded/evt.total*100, 10) + '%';
101 | };
102 | xhr.upload.onloadend = function (evt) {
103 | if ('serviceWorker' in navigator && !navigator.onLine && !navigator.serviceWorker.controller) {
104 |
105 | } else {
106 | messageElement.className = 'message success';
107 | messageElement.textContent = 'Your comment was posted sucessfully.';
108 | }
109 | };
110 | xhr.send(formData);
111 |
112 | // always call preventDefault at the end, see: http://molily.de/javascript-failure/
113 | ev.preventDefault();
114 | });
115 | }
116 |
117 | // auto-expand the textarea
118 | commentArea.addEventListener('keydown', autosize);
119 |
120 | function autosize(){
121 | var el = this;
122 | setTimeout(function(){
123 | el.style.cssText = 'height:auto;';
124 | el.style.cssText = 'height:' + el.scrollHeight + 'px';
125 | },0);
126 | }
127 |
128 |
129 |
--------------------------------------------------------------------------------
/form-enhancement/v7/idb-keyval.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | 'use strict';
3 | var db;
4 |
5 | function getDB() {
6 | if (!db) {
7 | db = new Promise(function(resolve, reject) {
8 | var openreq = indexedDB.open('keyval-store', 1);
9 |
10 | openreq.onerror = function() {
11 | reject(openreq.error);
12 | };
13 |
14 | openreq.onupgradeneeded = function() {
15 | // First time setup: create an empty object store
16 | openreq.result.createObjectStore('keyval');
17 | };
18 |
19 | openreq.onsuccess = function() {
20 | resolve(openreq.result);
21 | };
22 | });
23 | }
24 | return db;
25 | }
26 |
27 | function withStore(type, callback) {
28 | return getDB().then(function(db) {
29 | return new Promise(function(resolve, reject) {
30 | var transaction = db.transaction('keyval', type);
31 | transaction.oncomplete = function() {
32 | resolve();
33 | };
34 | transaction.onerror = function() {
35 | reject(transaction.error);
36 | };
37 | callback(transaction.objectStore('keyval'));
38 | });
39 | });
40 | }
41 |
42 | var idbKeyval = {
43 | get: function(key) {
44 | var req;
45 | return withStore('readonly', function(store) {
46 | req = store.get(key);
47 | }).then(function() {
48 | return req.result;
49 | });
50 | },
51 | set: function(key, value) {
52 | return withStore('readwrite', function(store) {
53 | store.put(value, key);
54 | });
55 | },
56 | delete: function(key) {
57 | return withStore('readwrite', function(store) {
58 | store.delete(key);
59 | });
60 | },
61 | clear: function() {
62 | return withStore('readwrite', function(store) {
63 | store.clear();
64 | });
65 | },
66 | keys: function() {
67 | var keys = [];
68 | return withStore('readonly', function(store) {
69 | // This would be store.getAllKeys(), but it isn't supported by Edge or Safari.
70 | // And openKeyCursor isn't supported by Safari.
71 | (store.openKeyCursor || store.openCursor).call(store).onsuccess = function() {
72 | if (!this.result) return;
73 | keys.push(this.result.key);
74 | this.result.continue();
75 | };
76 | }).then(function() {
77 | return keys;
78 | });
79 | }
80 | };
81 |
82 | if (typeof module != 'undefined' && module.exports) {
83 | module.exports = idbKeyval;
84 | } else {
85 | self.idbKeyval = idbKeyval;
86 | }
87 | }());
88 |
--------------------------------------------------------------------------------
/form-enhancement/v7/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Demo V7: Enhancing an form: From basic to custom error message to BackgroundSync
7 |
8 |
67 |
68 |
69 |
70 | Note: The comments are not saved in a database on submit. This example demonstrates the front-end but not the back-end.
71 |
72 | Comments
73 |
74 |
80 |
81 |
92 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/form-enhancement/v7/service-worker.js:
--------------------------------------------------------------------------------
1 | importScripts('idb-keyval.js');
2 |
3 | const VERSION = 'v1'
4 |
5 | self.addEventListener('install', function(event) {
6 | self.skipWaiting();
7 | event.waitUntil(
8 | caches.open(VERSION).then(function(cache) {
9 | return cache.addAll([
10 | './',
11 | './index.html',
12 | '../style.css',
13 | 'enhanced.js'
14 | ]);
15 | })
16 | );
17 | });
18 |
19 | self.addEventListener('fetch', function(event) {
20 | let request = event.request;
21 | if (request.method !== 'GET') {
22 | return;
23 | }
24 | event.respondWith(
25 | caches.match(request).then(function(response) {
26 | return response || fetch(request);
27 | })
28 | );
29 | });
30 |
31 | self.addEventListener('activate', function(event) {
32 | if (self.clients && clients.claim) {
33 | clients.claim();
34 | }
35 | });
36 |
37 | self.addEventListener('sync', function(event) {
38 | if (event.tag == 'form-post') {
39 | event.waitUntil(postComment());
40 | }
41 | });
42 |
43 | function postComment() {
44 |
45 | let formData = new FormData();
46 |
47 | idbKeyval.get('name').then(function (data) {
48 | formData.append( "name", data );
49 | });
50 | idbKeyval.get('comment').then(function (data) {
51 | formData.append( "comment", data );
52 | });
53 |
54 | fetch("./save",
55 | {
56 | method: "POST",
57 | mode: 'cors',
58 | body: formData
59 | })
60 | .then(function(response) {
61 | return response;
62 | })
63 | .then(function(text) {
64 | send_message_to_all_clients('success');
65 | })
66 | .catch(function(error) {
67 | send_message_to_all_clients('error');
68 | });
69 | }
70 |
71 | function send_message_to_client(client, msg){
72 | return new Promise(function(resolve, reject){
73 | var msg_chan = new MessageChannel();
74 |
75 | msg_chan.port1.onmessage = function(event){
76 | if(event.data.error){
77 | reject(event.data.error);
78 | }else{
79 | resolve(event.data);
80 | }
81 | };
82 |
83 | client.postMessage(msg, [msg_chan.port2]);
84 | });
85 | }
86 |
87 | function send_message_to_all_clients(msg){
88 | clients.matchAll().then(clients => {
89 | clients.forEach(client => {
90 | send_message_to_client(client, msg).then(m => console.log("SW Received Message: "+m));
91 | })
92 | })
93 | }
94 |
--------------------------------------------------------------------------------
/gifhancement/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Gifhancement
9 |
26 |
27 |
28 |
29 | Gifhancement
30 |
31 |
32 |
33 | Your browser doesn't support playing videos, but you can download it instead.
34 | Download as GIF
35 | Download as Video
36 |
37 |
38 |
39 |
40 | Your browser doesn't support playing videos, but you can download it instead.
41 | Download as GIF
42 | Download as Video
43 |
44 |
45 |
46 |
47 | Your browser doesn't support playing videos, but you can download it instead.
48 | Download as GIF
49 | Download as Video
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/gifhancement/script.js:
--------------------------------------------------------------------------------
1 | // https://git.io/vH4Ek
2 | var supports_video_autoplay = function(callback) {
3 |
4 | var v = document.createElement("video");
5 | v.paused = true;
6 | var p = false;
7 | try {
8 | p = "play" in v && v.play();
9 | } catch (err) {
10 |
11 | }
12 | typeof callback === "function" && callback(!v.paused || "Promise" in window && p instanceof Promise);
13 |
14 | };
15 |
16 | var supports_mp4_in_img = function(callback) {
17 |
18 | var image = new Image();
19 |
20 | image.onload = function() {
21 | var isSupported = image.width > 0 && image.height > 0;
22 | callback(isSupported);
23 | };
24 |
25 | image.onerror = function() {
26 | callback(false);
27 | };
28 | image.src = 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAAs1tZGF0AAACrgYF//+q3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE0OCByMjYwMSBhMGNkN2QzIC0gSC4yNjQvTVBFRy00IEFWQyBjb2RlYyAtIENvcHlsZWZ0IDIwMDMtMjAxNSAtIGh0dHA6Ly93d3cudmlkZW9sYW4ub3JnL3gyNjQuaHRtbCAtIG9wdGlvbnM6IGNhYmFjPTEgcmVmPTMgZGVibG9jaz0xOjA6MCBhbmFseXNlPTB4MzoweDExMyBtZT1oZXggc3VibWU9NyBwc3k9MSBwc3lfcmQ9MS4wMDowLjAwIG1peGVkX3JlZj0xIG1lX3JhbmdlPTE2IGNocm9tYV9tZT0xIHRyZWxsaXM9MSA4eDhkY3Q9MSBjcW09MCBkZWFkem9uZT0yMSwxMSBmYXN0X3Bza2lwPTEgY2hyb21hX3FwX29mZnNldD0tMiB0aHJlYWRzPTEgbG9va2FoZWFkX3RocmVhZHM9MSBzbGljZWRfdGhyZWFkcz0wIG5yPTAgZGVjaW1hdGU9MSBpbnRlcmxhY2VkPTAgYmx1cmF5X2NvbXBhdD0wIGNvbnN0cmFpbmVkX2ludHJhPTAgYmZyYW1lcz0zIGJfcHlyYW1pZD0yIGJfYWRhcHQ9MSBiX2JpYXM9MCBkaXJlY3Q9MSB3ZWlnaHRiPTEgb3Blbl9nb3A9MCB3ZWlnaHRwPTIga2V5aW50PTI1MCBrZXlpbnRfbWluPTEwIHNjZW5lY3V0PTQwIGludHJhX3JlZnJlc2g9MCByY19sb29rYWhlYWQ9NDAgcmM9Y3JmIG1idHJlZT0xIGNyZj0yMy4wIHFjb21wPTAuNjAgcXBtaW49MCBxcG1heD02OSBxcHN0ZXA9NCBpcF9yYXRpbz0xLjQwIGFxPTE6MS4wMACAAAAAD2WIhAA3//728P4FNjuZQQAAAu5tb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAAZAABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACGHRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAAZAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAgAAAAIAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAAGQAAAAAAAEAAAAAAZBtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAACgAAAAEAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAE7bWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAAA+3N0YmwAAACXc3RzZAAAAAAAAAABAAAAh2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAgACAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAAxYXZjQwFkAAr/4QAYZ2QACqzZX4iIhAAAAwAEAAADAFA8SJZYAQAGaOvjyyLAAAAAGHN0dHMAAAAAAAAAAQAAAAEAAAQAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAAsUAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjU2LjQwLjEwMQ==';
29 | }
30 |
31 | supports_video_autoplay(function(supportsAutoplay) {
32 | supports_mp4_in_img(function(supportsMP4InImg) {
33 | if (supportsMP4InImg) {
34 | var videos = document.querySelectorAll('[data-gif]');
35 |
36 | [].forEach.call(videos, function(video) {
37 | var img = new Image();
38 | img.src = video.querySelector('source').src;
39 | img.setAttribute('alt', "");
40 | if (img.decode) {
41 | img.decode().then(function() { video.parentNode.replaceChild(img, video); });
42 | } else {
43 | img.onload = function() {
44 | video.parentNode.replaceChild(img, video);
45 | }
46 | }
47 |
48 | });
49 | } else {
50 | var saveData = navigator.connection && navigator.connection.saveData;
51 | if (supportsAutoplay && 'IntersectionObserver' in window && !saveData) {
52 | var videos = document.querySelectorAll('[data-gif]');
53 |
54 | observer = new IntersectionObserver(entries => {
55 | entries.forEach(entry => {
56 | var video = entry.target;
57 | if (entry.intersectionRatio > 0) {
58 | // video is in the viewport - start it
59 | video.setAttribute('autoplay', true);
60 | video.setAttribute('loop', true);
61 | } else {
62 | // video is outside the viewport - pause it
63 | video.removeAttribute('autoplay');
64 | video.removeAttribute('loop');
65 | }
66 | });
67 | });
68 |
69 | [].forEach.call(videos, function(video) {
70 | video.removeAttribute('controls');
71 |
72 | observer.observe(video);
73 | });
74 | } else {
75 | // no video autoplay support :(
76 | }
77 | }
78 | });
79 |
80 | });
--------------------------------------------------------------------------------
/image-orientation/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
63 |
64 | # uploads
65 | uploads
--------------------------------------------------------------------------------
/image-orientation/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Michael Scharnagl
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/image-orientation/index.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const path = require("path");
3 | const fs = require("fs");
4 | const Jimp = require('jimp');
5 | const bodyParser = require('body-parser');
6 | const multer = require('multer');
7 |
8 | const modifyExif = require('modify-exif');
9 |
10 | var storage = multer.diskStorage({
11 | destination: function(req, file, cb) {
12 | cb(null, path.join(__dirname, "uploads"));
13 | },
14 | filename: function(req, file, cb) {
15 | cb(null, Date.now() + '__' + file.originalname);
16 | }
17 | })
18 |
19 | const app = express();
20 | const upload = multer({ storage: storage });
21 |
22 | const PORT = process.env.PORT || '3001';
23 |
24 | app.engine("pug", require("pug").__express);
25 | app.set("view engine", "pug");
26 | app.set("views", path.join(__dirname, "views"));
27 |
28 |
29 | app.use(bodyParser.urlencoded({ extended: true }));
30 | app.use(express.json());
31 | app.use(express.static(path.join(__dirname, "public")));
32 | app.use(express.static(path.join(__dirname, "uploads")));
33 |
34 | app.get('/show', async(req, res) => {
35 | const images = getImagesFromDir(path.join(__dirname, 'uploads'));
36 |
37 | res.render('show', {
38 | images: images
39 | });
40 | });
41 |
42 | app.get('/', (req, res) => {
43 | res.render('index');
44 | });
45 |
46 |
47 | app.post('/upload', upload.array('file'), async(req, res, next) => {
48 | const images = req.files;
49 |
50 | for (image of images) {
51 | await correctOrientation(image);
52 | }
53 |
54 | res.redirect('./show');
55 | });
56 |
57 |
58 | const readFileAsync = async(file) => {
59 | return await new Promise((resolve, reject) => {
60 | fs.readFile(file, async(err, data) => {
61 | err ? reject(err) : resolve(data);
62 | });
63 | });
64 | };
65 |
66 | const correctOrientation = async(image) => {
67 | let imageOrientation = false;
68 | let rotateDeg = 0;
69 |
70 | const buffer = modifyExif(await readFileAsync(path.join(__dirname, "uploads") + '/' + image.filename), data => {
71 | imageOrientation = data && data["0th"] && data["0th"]["274"] ? data["0th"]["274"] : false;
72 | if (imageOrientation) {
73 | if (imageOrientation === 1) {
74 | imageOrientation = false;
75 | } else {
76 | data["0th"]["274"] = 1; // reset EXIF orientation value
77 | }
78 | }
79 | });
80 |
81 | if (imageOrientation) {
82 | switch (imageOrientation) {
83 | case 3:
84 | rotateDeg = 180;
85 | break;
86 | case 6:
87 | rotateDeg = 270;
88 | break;
89 | case 8:
90 | rotateDeg = 90;
91 | reak;
92 | default:
93 | rotateDeg = 0;
94 | break;
95 | }
96 | Jimp.read(buffer, (err, lenna) => {
97 | if (err) {
98 | console.log('err', err);
99 | return;
100 | }
101 | lenna
102 | .rotate(rotateDeg) // correct orientation
103 | .write(path.join(__dirname, "uploads") + '/' + image.filename); // save
104 | });
105 | }
106 | };
107 |
108 |
109 | const getImagesFromDir = (dirPath) => {
110 | let allImages = [];
111 |
112 | let files = fs.readdirSync(dirPath);
113 |
114 | for (file of files) {
115 | let fileLocation = path.join(dirPath, file);
116 | var stat = fs.statSync(fileLocation);
117 | if (stat && stat.isDirectory()) {
118 | getImagesFromDir(fileLocation);
119 | } else if (stat && stat.isFile() && ['.jpg', '.png', '.PNG', '.JPG', '.jpeg'].indexOf(path.extname(fileLocation)) != -1) {
120 | allImages.push(file);
121 | }
122 | }
123 |
124 | return allImages.reverse();
125 | };
126 |
127 | app.listen(PORT);
128 | console.log('app running on port ', PORT);
--------------------------------------------------------------------------------
/image-orientation/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "image-orientation",
3 | "version": "0.0.1",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "nodemon index.js",
9 | "start": "node index.js"
10 | },
11 | "keywords": [],
12 | "author": "Michael Scharnagl",
13 | "license": "MIT",
14 | "engines": {
15 | "node": "10.x.x"
16 | },
17 | "dependencies": {
18 | "body-parser": "^1.19.0",
19 | "express": "^4.17.1",
20 | "jimp": "^0.8.4",
21 | "modify-exif": "0.0.1",
22 | "multer": "^1.4.2",
23 | "pug": "^2.0.4"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/image-orientation/public/css/main.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0 auto;
4 | width: 800px;
5 | max-width: 90%;
6 | font-family: monospace;
7 | color: #1c2020;
8 | font-size: 1.2em;
9 | }
10 |
11 | img {
12 | max-width: 100%;
13 | height: auto;
14 | display: block;
15 | border: 4px solid black;
16 | margin: 10px 0;
17 | }
18 |
19 | h1 {
20 | text-align: center;
21 | font-weight: normal;
22 | }
23 |
24 | .images {
25 | list-style: none;
26 | margin: 0;
27 | padding: 0;
28 | }
29 |
30 | form {
31 | background: #eee;
32 | padding: 20px;
33 | display: flex;
34 | justify-content: space-around;
35 | }
--------------------------------------------------------------------------------
/image-orientation/uploads/notempty.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/image-orientation/uploads/notempty.txt
--------------------------------------------------------------------------------
/image-orientation/views/index.pug:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 |
5 | div.wrapper
6 |
7 | h1(class="hl--alpha") Upload
8 |
9 | form(action="./upload", enctype="multipart/form-data", method="POST", class="form")
10 | label(for="file") Select images:
11 | input(type="file", required, name="file", multiple, accept="image/*")
12 | input(type="submit", value="Upload your photos")
13 |
14 |
--------------------------------------------------------------------------------
/image-orientation/views/layout.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html(lang="en")
3 | head
4 | if title
5 | title #{title} | image-orientation
6 | else
7 | title image-orientation
8 |
9 | meta(name="viewport", content="width=device-width, initial-scale=1")
10 | meta(name="theme-color", content="#50A")
11 |
12 | link(href="./css/main.css", rel="stylesheet")
13 |
14 | body
15 |
16 | block content
--------------------------------------------------------------------------------
/image-orientation/views/show.pug:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 |
5 | ul.images
6 | each image in images
7 | li
8 | img(src=image, loading="lazy", width="800", alt="")
--------------------------------------------------------------------------------
/indicating-offline/article-1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | An article worth reading
7 |
64 |
65 |
66 | Online
67 |
68 | An article worth reading
69 |
70 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
71 |
72 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
73 |
74 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
75 |
76 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/indicating-offline/article-2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Don't miss this article
7 |
64 |
65 |
66 | Online
67 |
68 | Don't miss this article
69 |
70 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
71 |
72 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
73 |
74 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
75 |
76 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/indicating-offline/article-3.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Another great article
7 |
64 |
65 |
66 | Online
67 |
68 | Another great article
69 |
70 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
71 |
72 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
73 |
74 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
75 |
76 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/indicating-offline/article-4.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | You will never believe what happend next
7 |
64 |
65 |
66 | Online
67 |
68 | You will never believe what happend next
69 |
70 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
71 |
72 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
73 |
74 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
75 |
76 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/indicating-offline/article-5.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Clickbait, Clickbait, Clickbait...
7 |
64 |
65 |
66 | Online
67 |
68 | Clickbait, Clickbait, Clickbait...
69 |
70 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
71 |
72 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
73 |
74 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum? Lorem ipsum dolor sit amet, consectetur adipisicing elit. Amet explicabo quidem sit voluptates quisquam itaque consectetur eos accusantium optio officia repellat provident laboriosam qui inventore, placeat ipsum velit doloremque rerum?
75 |
76 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/.bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sw-toolbox",
3 | "main": "sw-toolbox.js",
4 | "moduleType": [
5 | "globals"
6 | ],
7 | "license": "Apache-2.0",
8 | "ignore": [
9 | "**/.*",
10 | "lib",
11 | "tests",
12 | "gulpfile.js"
13 | ],
14 | "homepage": "https://github.com/GoogleChrome/sw-toolbox",
15 | "version": "3.2.1",
16 | "_release": "3.2.1",
17 | "_resolution": {
18 | "type": "version",
19 | "tag": "v3.2.1",
20 | "commit": "2a979f3b20a15264e162636bf998e9c45b8f9e39"
21 | },
22 | "_source": "https://github.com/GoogleChrome/sw-toolbox.git",
23 | "_target": "~3.2.1",
24 | "_originalSource": "sw-toolbox",
25 | "_direct": true
26 | }
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/README.md:
--------------------------------------------------------------------------------
1 | # Service Worker Toolbox
2 |
3 | [](https://travis-ci.org/GoogleChrome/sw-toolbox) [](https://david-dm.org/googlechrome/sw-toolbox) [](https://david-dm.org/googlechrome/sw-toolbox#info=devDependencies)
4 |
5 | > A collection of tools for [service workers](https://slightlyoff.github.io/ServiceWorker/spec/service_worker/)
6 |
7 | Service Worker Toolbox provides some simple helpers for use in creating your own service workers. Specifically, it provides common caching patterns and an [expressive approach](https://googlechrome.github.io/sw-toolbox/docs/master/tutorial-api#expressive-approach) to using those strategies for runtime requests. If you're not sure what service workers are or what they are for, start with [the explainer doc](https://github.com/slightlyoff/ServiceWorker/blob/master/explainer.md).
8 |
9 | ## Install
10 |
11 | Service Worker Toolbox is available through Bower, npm or direct from GitHub:
12 |
13 | `bower install --save sw-toolbox`
14 |
15 | `npm install --save sw-toolbox`
16 |
17 | `git clone https://github.com/GoogleChrome/sw-toolbox.git`
18 |
19 | ### Register your service worker
20 |
21 | From your registering page, register your service worker in the normal way. For example:
22 |
23 | ```javascript
24 | navigator.serviceWorker.register('my-service-worker.js');
25 | ```
26 | As implemented in Chrome 40 or later, a service worker must exist at the root of the scope that you intend it to control, or higher. So if you want all of the pages under `/myapp/` to be controlled by the worker, the worker script itself must be served from either `/` or `/myapp/`. The default scope is the containing path of the service worker script.
27 |
28 | For even lower friction you can instead include the Service Worker Toolbox companion script in your HTML as shown below. Be aware that this is not customizable. If you need to do anything fancier than registering with a default scope, you'll need to use the standard registration.
29 |
30 | ```html
31 |
32 | ```
33 |
34 | ### Add Service Worker Toolbox to your service worker script
35 |
36 | In your service worker you just need to use `importScripts` to load Service Worker Toolbox
37 |
38 | ```javascript
39 | importScripts('bower_components/sw-toolbox/sw-toolbox.js'); // Update path to match your own setup
40 | ```
41 |
42 | ### Use the toolbox
43 |
44 | To understand how to use the toolbox read the [usage](https://googlechrome.github.io/sw-toolbox/docs/master/tutorial-usage) and [api](https://googlechrome.github.io/sw-toolbox/docs/master/tutorial-api) documentation.
45 |
46 | ## Support
47 |
48 | If you’ve found an error in this library, please file an issue at: https://github.com/GoogleChrome/sw-toolbox/issues.
49 |
50 | Patches are encouraged, and may be submitted by forking this project and submitting a pull request through GitHub.
51 |
52 | ## License
53 |
54 | Copyright 2015 Google, Inc.
55 |
56 | Licensed under the [Apache License, Version 2.0](LICENSE) (the "License");
57 | you may not use this file except in compliance with the License. You may
58 | obtain a copy of the License at
59 |
60 | http://www.apache.org/licenses/LICENSE-2.0
61 |
62 | Unless required by applicable law or agreed to in writing, software
63 | distributed under the License is distributed on an "AS IS" BASIS,
64 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
65 | See the License for the specific language governing permissions and
66 | limitations under the License.
67 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sw-toolbox",
3 | "main": "sw-toolbox.js",
4 | "moduleType": [
5 | "globals"
6 | ],
7 | "license": "Apache-2.0",
8 | "ignore": [
9 | "**/.*",
10 | "lib",
11 | "tests",
12 | "gulpfile.js"
13 | ]
14 | }
15 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/cache-expiration-options/app.js:
--------------------------------------------------------------------------------
1 | /* eslint-env browser */
2 | 'use strict';
3 |
4 | // Please register for your own YouTube API key!
5 | // https://developers.google.com/youtube/v3/getting-started#before-you-start
6 | const API_KEY = 'AIzaSyC4trKMxwT42TUFHmikCc4xxQTWWxq5S0g';
7 | const API_URL = 'https://www.googleapis.com/youtube/v3/search';
8 |
9 | function serializeUrlParams(params) {
10 | return Object.keys(params).map(key => {
11 | return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
12 | }).join('&');
13 | }
14 |
15 | function youtubeSearch(searchTerm, maxResults) {
16 | let params = {
17 | part: 'snippet',
18 | maxResults: maxResults,
19 | order: 'date',
20 | key: API_KEY,
21 | q: searchTerm
22 | };
23 |
24 | let url = new URL(API_URL);
25 | url.search = serializeUrlParams(params);
26 |
27 | return fetch(url).then(response => {
28 | if (response.ok) {
29 | return response.json();
30 | }
31 | throw new Error(`${response.status}: ${response.statusText}`);
32 | }).then(function(json) {
33 | return json.items;
34 | });
35 | }
36 |
37 | document.querySelector('#search').addEventListener('submit', event => {
38 | event.preventDefault();
39 |
40 | var results = document.querySelector('#results');
41 | while (results.firstChild) {
42 | results.removeChild(results.firstChild);
43 | }
44 |
45 | let searchTerm = document.querySelector('#searchTerm').value;
46 | let maxResults = document.querySelector('#maxResults').value;
47 |
48 | youtubeSearch(searchTerm, maxResults).then(videos => {
49 | videos.forEach(video => {
50 | let img = document.createElement('img');
51 | img.src = video.snippet.thumbnails.medium.url;
52 | results.appendChild(img);
53 | });
54 | }).catch(error => console.warn('YouTube search failed due to', error));
55 | });
56 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/cache-expiration-options/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Cache Expiration Demo
4 |
5 |
6 |
22 |
23 |
24 |
25 | Cache Expiration Demo
26 |
27 | Background
28 |
29 | The service worker in this example
30 | demonstrates using the maxCacheEntries
and maxCacheAgeSeconds
31 | options. It uses a dedicated cache to hold YouTube video thumbnails. That
32 | dedicated cache will purge entries once they're older than 30 seconds, and store at most 10
33 | entries. It uses the cacheFirst
strategy, so any responses that are still in the
34 | cache will be used directly, without going against the network.
35 |
36 |
37 |
38 | While this example uses both maxCacheEntries
and maxCacheAgeSeconds
,
39 | it's possible to use each of those options independently.
40 |
41 |
42 |
43 | The cache used for YouTube thumbnail URLs is separate from the "default" cache, which is
44 | used for all other requests, like YouTube API responses and this page's CSS, JavaScript, and
45 | HTML. The page doesn't impose any upper limit on the size of that default cache, and we can
46 | use a networkFirst
strategy for it.
47 |
48 |
49 |
50 | Creating a dedicated cache with expiration options for dynamic, unbounded requests is a useful
51 | pattern to follow. If we just used the default cache without imposing a cache expiration,
52 | then that cache would grow in size as more and more searches were performed, needlessly
53 | consuming disk space for old thumbnails that are likely no longer needed.
54 |
55 |
56 | Live Demo
57 |
58 | Try increasing the number of thumbnails returned, or changing the search term, and then
59 | observe the cache expirations logged in the developer console.
60 |
61 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/cache-expiration-options/service-worker.js:
--------------------------------------------------------------------------------
1 | (global => {
2 | 'use strict';
3 |
4 | // Load the sw-tookbox library.
5 | importScripts('../../sw-toolbox.js');
6 |
7 | // Turn on debug logging, visible in the Developer Tools' console.
8 | global.toolbox.options.debug = true;
9 |
10 | // Set up a handler for HTTP GET requests:
11 | // - /\.ytimg\.com\// will match any requests whose URL contains 'ytimg.com'.
12 | // A narrower RegExp could be used, but just checking for ytimg.com anywhere
13 | // in the URL should be fine for this sample.
14 | // - toolbox.cacheFirst let us to use the predefined cache strategy for those
15 | // requests.
16 | global.toolbox.router.get(/\.ytimg\.com\//, global.toolbox.cacheFirst, {
17 | // Use a dedicated cache for the responses, separate from the default cache.
18 | cache: {
19 | name: 'youtube-thumbnails',
20 | // Store up to 10 entries in that cache.
21 | maxEntries: 10,
22 | // Expire any entries that are older than 30 seconds.
23 | maxAgeSeconds: 30
24 | }
25 | });
26 |
27 | // By default, all requests that don't match our custom handler will use the
28 | // toolbox.networkFirst cache strategy, and their responses will be stored in
29 | // the default cache.
30 | global.toolbox.router.default = global.toolbox.networkFirst;
31 |
32 | // Boilerplate to ensure our service worker takes control of the page as soon
33 | // as possible.
34 | global.addEventListener('install',
35 | event => event.waitUntil(global.skipWaiting()));
36 | global.addEventListener('activate',
37 | event => event.waitUntil(global.clients.claim()));
38 | })(self);
39 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/cache-expiration-options/styles.css:
--------------------------------------------------------------------------------
1 | #results {
2 | display: flex;
3 | flex-direction: row;
4 | flex-wrap: wrap;
5 | }
6 |
7 | #results > img {
8 | margin: 4px;
9 | width: 320px;
10 | }
11 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Bold-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Bold-webfont.eot
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Bold-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Bold-webfont.woff
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-BoldItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-BoldItalic-webfont.eot
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-BoldItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-BoldItalic-webfont.woff
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Italic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Italic-webfont.eot
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Italic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Italic-webfont.woff
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Light-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Light-webfont.eot
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Light-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Light-webfont.woff
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-LightItalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-LightItalic-webfont.eot
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-LightItalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-LightItalic-webfont.woff
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Regular-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Regular-webfont.eot
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Regular-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/indicating-offline/bower_components/sw-toolbox/docs/fonts/OpenSans-Regular-webfont.woff
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Home
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Home
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | Service Worker Toolbox
47 |
48 | A collection of tools for service workers
49 |
50 | Service Worker Toolbox provides some simple helpers for use in creating your own service workers. Specifically, it provides common caching patterns and an expressive approach to using those strategies for runtime requests. If you're not sure what service workers are or what they are for, start with the explainer doc .
51 | Install Service Worker Toolbox is available through Bower, npm or direct from GitHub:
52 | bower install --save sw-toolbox
53 | npm install --save sw-toolbox
54 | git clone https://github.com/GoogleChrome/sw-toolbox.git
55 | Register your service worker From your registering page, register your service worker in the normal way. For example:
56 | navigator.serviceWorker.register('my-service-worker.js');
As implemented in Chrome 40 or later, a service worker must exist at the root of the scope that you intend it to control, or higher. So if you want all of the pages under /myapp/
to be controlled by the worker, the worker script itself must be served from either /
or /myapp/
. The default scope is the containing path of the service worker script.
57 | For even lower friction you can instead include the Service Worker Toolbox companion script in your HTML as shown below. Be aware that this is not customizable. If you need to do anything fancier than registering with a default scope, you'll need to use the standard registration.
58 | <script src="/path/to/sw-toolbox/companion.js" data-service-worker="my-service-worker.js"></script>
Add Service Worker Toolbox to your service worker script In your service worker you just need to use importScripts
to load Service Worker Toolbox
59 | importScripts('bower_components/sw-toolbox/sw-toolbox.js'); // Update path to match your own setup
Use the toolbox To understand how to use the toolbox read the usage and api documentation.
60 | Support If you’ve found an error in this library, please file an issue at: https://github.com/GoogleChrome/sw-toolbox/issues.
61 | Patches are encouraged, and may be submitted by forking this project and submitting a pull request through GitHub.
62 | License Copyright 2015 Google, Inc.
63 | Licensed under the Apache License, Version 2.0 (the "License");
64 | you may not use this file except in compliance with the License. You may
65 | obtain a copy of the License at
66 | http://www.apache.org/licenses/LICENSE-2.0
67 | Unless required by applicable law or agreed to in writing, software
68 | distributed under the License is distributed on an "AS IS" BASIS,
69 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
70 | See the License for the specific language governing permissions and
71 | limitations under the License.
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 | Tutorials
83 |
84 |
85 |
86 |
87 |
88 | Documentation generated by JSDoc 3.4.0 on Thu Jun 09 2016 13:34:34 GMT+0100 (BST)
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/scripts/linenumber.js:
--------------------------------------------------------------------------------
1 | /*global document */
2 | (function() {
3 | var source = document.getElementsByClassName('prettyprint source linenums');
4 | var i = 0;
5 | var lineNumber = 0;
6 | var lineId;
7 | var lines;
8 | var totalLines;
9 | var anchorHash;
10 |
11 | if (source && source[0]) {
12 | anchorHash = document.location.hash.substring(1);
13 | lines = source[0].getElementsByTagName('li');
14 | totalLines = lines.length;
15 |
16 | for (; i < totalLines; i++) {
17 | lineNumber++;
18 | lineId = 'line' + lineNumber;
19 | lines[i].id = lineId;
20 | if (lineId === anchorHash) {
21 | lines[i].className += ' selected';
22 | }
23 | }
24 | }
25 | })();
26 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/scripts/prettify/lang-css.js:
--------------------------------------------------------------------------------
1 | PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n"]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
2 | /^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);
3 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/styles/prettify-jsdoc.css:
--------------------------------------------------------------------------------
1 | /* JSDoc prettify.js theme */
2 |
3 | /* plain text */
4 | .pln {
5 | color: #000000;
6 | font-weight: normal;
7 | font-style: normal;
8 | }
9 |
10 | /* string content */
11 | .str {
12 | color: #006400;
13 | font-weight: normal;
14 | font-style: normal;
15 | }
16 |
17 | /* a keyword */
18 | .kwd {
19 | color: #000000;
20 | font-weight: bold;
21 | font-style: normal;
22 | }
23 |
24 | /* a comment */
25 | .com {
26 | font-weight: normal;
27 | font-style: italic;
28 | }
29 |
30 | /* a type name */
31 | .typ {
32 | color: #000000;
33 | font-weight: normal;
34 | font-style: normal;
35 | }
36 |
37 | /* a literal value */
38 | .lit {
39 | color: #006400;
40 | font-weight: normal;
41 | font-style: normal;
42 | }
43 |
44 | /* punctuation */
45 | .pun {
46 | color: #000000;
47 | font-weight: bold;
48 | font-style: normal;
49 | }
50 |
51 | /* lisp open bracket */
52 | .opn {
53 | color: #000000;
54 | font-weight: bold;
55 | font-style: normal;
56 | }
57 |
58 | /* lisp close bracket */
59 | .clo {
60 | color: #000000;
61 | font-weight: bold;
62 | font-style: normal;
63 | }
64 |
65 | /* a markup tag name */
66 | .tag {
67 | color: #006400;
68 | font-weight: normal;
69 | font-style: normal;
70 | }
71 |
72 | /* a markup attribute name */
73 | .atn {
74 | color: #006400;
75 | font-weight: normal;
76 | font-style: normal;
77 | }
78 |
79 | /* a markup attribute value */
80 | .atv {
81 | color: #006400;
82 | font-weight: normal;
83 | font-style: normal;
84 | }
85 |
86 | /* a declaration */
87 | .dec {
88 | color: #000000;
89 | font-weight: bold;
90 | font-style: normal;
91 | }
92 |
93 | /* a variable name */
94 | .var {
95 | color: #000000;
96 | font-weight: normal;
97 | font-style: normal;
98 | }
99 |
100 | /* a function name */
101 | .fun {
102 | color: #000000;
103 | font-weight: bold;
104 | font-style: normal;
105 | }
106 |
107 | /* Specify class=linenums on a pre to get line numbering */
108 | ol.linenums {
109 | margin-top: 0;
110 | margin-bottom: 0;
111 | }
112 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/styles/prettify-tomorrow.css:
--------------------------------------------------------------------------------
1 | /* Tomorrow Theme */
2 | /* Original theme - https://github.com/chriskempson/tomorrow-theme */
3 | /* Pretty printing styles. Used with prettify.js. */
4 | /* SPAN elements with the classes below are added by prettyprint. */
5 | /* plain text */
6 | .pln {
7 | color: #4d4d4c; }
8 |
9 | @media screen {
10 | /* string content */
11 | .str {
12 | color: #718c00; }
13 |
14 | /* a keyword */
15 | .kwd {
16 | color: #8959a8; }
17 |
18 | /* a comment */
19 | .com {
20 | color: #8e908c; }
21 |
22 | /* a type name */
23 | .typ {
24 | color: #4271ae; }
25 |
26 | /* a literal value */
27 | .lit {
28 | color: #f5871f; }
29 |
30 | /* punctuation */
31 | .pun {
32 | color: #4d4d4c; }
33 |
34 | /* lisp open bracket */
35 | .opn {
36 | color: #4d4d4c; }
37 |
38 | /* lisp close bracket */
39 | .clo {
40 | color: #4d4d4c; }
41 |
42 | /* a markup tag name */
43 | .tag {
44 | color: #c82829; }
45 |
46 | /* a markup attribute name */
47 | .atn {
48 | color: #f5871f; }
49 |
50 | /* a markup attribute value */
51 | .atv {
52 | color: #3e999f; }
53 |
54 | /* a declaration */
55 | .dec {
56 | color: #f5871f; }
57 |
58 | /* a variable name */
59 | .var {
60 | color: #c82829; }
61 |
62 | /* a function name */
63 | .fun {
64 | color: #4271ae; } }
65 | /* Use higher contrast and text-weight for printable form. */
66 | @media print, projection {
67 | .str {
68 | color: #060; }
69 |
70 | .kwd {
71 | color: #006;
72 | font-weight: bold; }
73 |
74 | .com {
75 | color: #600;
76 | font-style: italic; }
77 |
78 | .typ {
79 | color: #404;
80 | font-weight: bold; }
81 |
82 | .lit {
83 | color: #044; }
84 |
85 | .pun, .opn, .clo {
86 | color: #440; }
87 |
88 | .tag {
89 | color: #006;
90 | font-weight: bold; }
91 |
92 | .atn {
93 | color: #404; }
94 |
95 | .atv {
96 | color: #060; } }
97 | /* Style */
98 | /*
99 | pre.prettyprint {
100 | background: white;
101 | font-family: Consolas, Monaco, 'Andale Mono', monospace;
102 | font-size: 12px;
103 | line-height: 1.5;
104 | border: 1px solid #ccc;
105 | padding: 10px; }
106 | */
107 |
108 | /* Specify class=linenums on a pre to get line numbering */
109 | ol.linenums {
110 | margin-top: 0;
111 | margin-bottom: 0; }
112 |
113 | /* IE indents via margin-left */
114 | li.L0,
115 | li.L1,
116 | li.L2,
117 | li.L3,
118 | li.L4,
119 | li.L5,
120 | li.L6,
121 | li.L7,
122 | li.L8,
123 | li.L9 {
124 | /* */ }
125 |
126 | /* Alternate shading for lines */
127 | li.L1,
128 | li.L3,
129 | li.L5,
130 | li.L7,
131 | li.L9 {
132 | /* */ }
133 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/docs/tutorial-recipes.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | JSDoc: Tutorial: Service Worker Toolbox Recipes
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
Tutorial: Service Worker Toolbox Recipes
21 |
22 |
23 |
24 |
25 |
26 |
27 | Service Worker Toolbox Recipes
28 |
29 |
30 |
31 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Tutorials
49 |
50 |
51 |
52 |
53 |
54 | Documentation generated by JSDoc 3.4.0 on Thu Jun 09 2016 13:34:34 GMT+0100 (BST)
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/indicating-offline/bower_components/sw-toolbox/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sw-toolbox",
3 | "version": "3.2.1",
4 | "license": "Apache-2.0",
5 | "scripts": {
6 | "publish-release": "./node_modules/sw-testing-helpers/project/publish-release.sh",
7 | "build": "gulp default",
8 | "build-docs": "jsdoc -c jsdoc.json && cp ./build/sw-toolbox.js ./docs/",
9 | "test": "gulp lint && gulp test:automated",
10 | "bundle": "./project/create-release-bundle.sh"
11 | },
12 | "main": "lib/sw-toolbox.js",
13 | "repository": "https://github.com/GoogleChrome/sw-toolbox",
14 | "dependencies": {
15 | "serviceworker-cache-polyfill": "^4.0.0",
16 | "path-to-regexp": "^1.0.1"
17 | },
18 | "devDependencies": {
19 | "browserify": "^12.0.1",
20 | "browserify-header": "^0.9.2",
21 | "chai": "^3.4.1",
22 | "chromedriver": "^2.20.0",
23 | "cookie-parser": "^1.4.1",
24 | "eslint": "^1.10.3",
25 | "eslint-config-google": "^0.3.0",
26 | "express": "^4.13.3",
27 | "gulp": "^3.9.0",
28 | "gulp-eslint": "^1.1.1",
29 | "gulp-gh-pages": "^0.5.4",
30 | "gulp-mocha": "^2.2.0",
31 | "jsdoc": "^3.4.0",
32 | "jshint-stylish": "^2.1.0",
33 | "minifyify": "^7.1.0",
34 | "mocha": "^2.3.4",
35 | "qunitjs": "^1.20.0",
36 | "selenium-webdriver": "^2.48.2",
37 | "sw-testing-helpers": "0.0.14",
38 | "temp": "^0.8.3",
39 | "vinyl-source-stream": "^1.1.0",
40 | "which": "^1.2.4"
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/indicating-offline/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Grey-out non-cached elements
7 |
70 |
71 |
72 |
73 |
74 | Online
75 |
76 | Latest articles
77 |
78 |
85 |
86 | Description
87 |
88 | Your browser needs to support Service Worker (no warning message shown). Open on of the above links to articles -> go back to this page -> go offline -> All links expect the ones you visited should be greyed-out.
89 |
90 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/indicating-offline/service-worker.js:
--------------------------------------------------------------------------------
1 | (global => {
2 | 'use strict';
3 |
4 | importScripts('bower_components/sw-toolbox/sw-toolbox.js');
5 |
6 | global.toolbox.options.debug = true;
7 |
8 | global.toolbox.router.get(/article-/, global.toolbox.cacheFirst, {
9 | // Use a dedicated cache for the responses, separate from the default cache.
10 | cache: {
11 | name: 'articles',
12 | // Store up to 10 articles in that cache.
13 | maxEntries: 10
14 | }
15 | });
16 |
17 | global.toolbox.router.default = global.toolbox.networkFirst;
18 |
19 | // Boilerplate to ensure our service worker takes control of the page as soon
20 | // as possible.
21 | global.addEventListener('install',
22 | event => event.waitUntil(global.skipWaiting()));
23 | global.addEventListener('activate',
24 | event => event.waitUntil(global.clients.claim()));
25 |
26 | })(self);
27 |
--------------------------------------------------------------------------------
/link-to-button/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Login Link to Login Overlay
9 |
10 |
11 |
12 |
13 |
14 |
23 |
24 | Login Link to Login Overlay example
25 |
26 | Some Content here
27 |
28 |
29 | The footer of the page
30 |
31 |
32 |
33 |
34 |
35 |
36 | ×
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/link-to-button/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Login Link to Login Overlay
9 |
10 |
11 |
12 |
13 |
14 |
23 |
24 | Login
25 |
43 |
44 |
45 | The footer of the page
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/link-to-button/script.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var dialogOpeners = document.querySelectorAll('[data-open-overlay]') || false;
3 |
4 | if (dialogOpeners && dialogOpeners.length > 0) {
5 | Array.prototype.forEach.call(dialogOpeners, dialogOpener => {
6 | var dialogContent = document.querySelector('[data-dialog-content]');
7 | window.dialog = false;
8 |
9 | dialogOpener.outerHTML = `
10 |
11 | ${dialogOpener.textContent}
12 |
13 | `;
14 |
15 | dialogOpener = document.querySelector('[data-link="' + dialogOpener.href + '"]')
16 |
17 | if (!window.dialog) {
18 | var dialogElement = document.getElementById('dialog');
19 | var mainEl = document.querySelector('#content');
20 | window.dialog = new A11yDialog(dialogElement, mainEl);
21 |
22 | dialogElement.removeAttribute('hidden');
23 |
24 | window.dialog.on('show', function(dialogEl, event) {
25 | console.log('opening the dialog')
26 | });
27 |
28 | window.dialog.on('hide', function(dialogEl, event) {
29 | console.log('closing the dialog');
30 | dialogContent.innerHTML = '';
31 | });
32 | }
33 |
34 | dialogOpener.addEventListener('click', function(ev) {
35 | var link = this.dataset.link ? this.dataset.link : false;
36 |
37 | console.log('click', link);
38 |
39 | if (link) {
40 |
41 | fetch(link)
42 | .then(function(response) {
43 | return response.text();
44 | })
45 | .then(function(html) {
46 | var parser = new DOMParser();
47 | var doc = parser.parseFromString(html, "text/html");
48 | var pageContent = doc.querySelector('#main');
49 | dialogContent.innerHTML = '';
50 |
51 | if (dialogContent && pageContent) {
52 | dialogContent.innerHTML = pageContent.innerHTML;
53 | window.dialog.show();
54 | } else {
55 | document.location = link;
56 | }
57 |
58 | })
59 | .catch(function(err) {
60 | console.log('Failed to fetch page: ', err);
61 | document.location = link;
62 | });
63 | ev.preventDefault();
64 | }
65 |
66 | });
67 | });
68 | }
69 | }());
--------------------------------------------------------------------------------
/link-to-button/style.css:
--------------------------------------------------------------------------------
1 | /* -------------------------------------------------------------------------- *\
2 | * Necessary styling for the dialog to work
3 | * -------------------------------------------------------------------------- */
4 |
5 |
6 | /**
7 | * When `` is properly supported, the overlay is implied and can be
8 | * styled with `::backdrop`, which means the DOM one should be removed.
9 | */
10 |
11 | [data-a11y-dialog-native] .dialog-overlay {
12 | display: none;
13 | }
14 |
15 |
16 | /**
17 | * When `` is not supported, its default display is `inline` which can
18 | * cause layout issues.
19 | */
20 |
21 | dialog[open] {
22 | display: block;
23 | }
24 |
25 | .dialog[aria-hidden="true"] {
26 | display: none;
27 | }
28 |
29 |
30 | /* -------------------------------------------------------------------------- *\
31 | * Styling to make the dialog look like a dialog
32 | * -------------------------------------------------------------------------- */
33 |
34 | .dialog-overlay {
35 | z-index: 2;
36 | background-color: rgba(0, 0, 0, 0.66);
37 | position: fixed;
38 | top: 0;
39 | left: 0;
40 | bottom: 0;
41 | right: 0;
42 | }
43 |
44 | dialog::backdrop {
45 | background-color: rgba(0, 0, 0, 0.66);
46 | }
47 |
48 | .dialog-content {
49 | background-color: rgb(255, 255, 255);
50 | z-index: 3;
51 | position: fixed;
52 | top: 50%;
53 | left: 50%;
54 | -webkit-transform: translate(-50%, -50%);
55 | -ms-transform: translate(-50%, -50%);
56 | transform: translate(-50%, -50%);
57 | margin: 0;
58 | border: 2px solid black;
59 | }
60 |
61 |
62 | /* -------------------------------------------------------------------------- *\
63 | * Extra dialog styling to make it shiny
64 | * -------------------------------------------------------------------------- */
65 |
66 | @keyframes fade-in {
67 | from {
68 | opacity: 0;
69 | }
70 | to {
71 | opacity: 1;
72 | }
73 | }
74 |
75 | @keyframes appear {
76 | from {
77 | transform: translate(-50%, -40%);
78 | opacity: 0;
79 | }
80 | to {
81 | transform: translate(-50%, -50%);
82 | opacity: 1;
83 | }
84 | }
85 |
86 | .dialog:not([aria-hidden='true'])>.dialog-overlay {
87 | animation: fade-in 200ms 1 both;
88 | }
89 |
90 | .dialog:not([aria-hidden='true'])>.dialog-content {
91 | animation: appear 400ms 150ms 1 both;
92 | }
93 |
94 | .dialog-content {
95 | padding: 1em;
96 | max-width: 90%;
97 | width: 600px;
98 | border-radius: 2px;
99 | }
100 |
101 | @media screen and (min-width: 700px) {
102 | .dialog-content {
103 | padding: 2em;
104 | }
105 | }
106 |
107 | .dialog-overlay {
108 | background-color: rgba(43, 46, 56, 0.9);
109 | }
110 |
111 | .dialog h1 {
112 | margin: 0;
113 | font-size: 1.5em;
114 | }
115 |
116 | .dialog-close {
117 | position: absolute;
118 | top: 0.5em;
119 | right: 0.5em;
120 | border: 0;
121 | padding: 0;
122 | background-color: transparent;
123 | font-weight: bold;
124 | font-size: 1.25em;
125 | width: 1.2em;
126 | height: 1.2em;
127 | text-align: center;
128 | cursor: pointer;
129 | transition: 0.15s;
130 | }
131 |
132 | @media screen and (min-width: 700px) {
133 | .dialog-close {
134 | top: 1em;
135 | right: 1em;
136 | }
137 | }
138 |
139 |
140 | /* -------------------------------------------------------------------------- *\
141 | * Base stuff
142 | * -------------------------------------------------------------------------- */
143 |
144 | * {
145 | box-sizing: border-box;
146 | }
147 |
148 | html,
149 | body {
150 | margin: 0;
151 | }
152 |
153 | body {
154 | font-family: Arial, Helvetica, sans-serif;
155 | }
156 |
157 | p,
158 | li {
159 | font-size: 1.4rem;
160 | }
161 |
162 | h1 {
163 | font-size: 2rem;
164 | line-height: 1.1;
165 | }
166 |
167 | h1,
168 | h2 {
169 | margin-bottom: 0;
170 | }
171 |
172 | .link-like,
173 | a {
174 | color: #0d2b9f;
175 | }
176 |
177 | .link-like:hover,
178 | .link-like:active,
179 | a:hover,
180 | a:active {
181 | color: #8802ca;
182 | }
183 |
184 |
185 | /* -------------------------------------------------------------------------- *\
186 | * Helpers
187 | * -------------------------------------------------------------------------- */
188 |
189 | .link-like {
190 | background-color: transparent;
191 | text-decoration: underline;
192 | border: 0;
193 | margin: 0;
194 | padding: 0;
195 | font: inherit;
196 | cursor: pointer;
197 | }
198 |
199 |
200 | /* -------------------------------------------------------------------------- *\
201 | * Layout
202 | * -------------------------------------------------------------------------- */
203 |
204 | nav {
205 | background: #eee;
206 | padding: 20px;
207 | }
208 |
209 | nav ul {
210 | list-style: none;
211 | margin: 0;
212 | padding: 0;
213 | }
214 |
215 | nav li {
216 | display: inline-block;
217 | padding: 0 20px 0 0;
218 | }
219 |
220 | .content {
221 | max-width: 700px;
222 | margin: 0 auto;
223 | padding: 0 1em;
224 | }
225 |
226 | footer {
227 | border-top: 1px solid #333;
228 | margin-top: 40px;
229 | padding: 0 0 20px 0;
230 | }
231 |
232 |
233 | /* -------------------------------------------------------------------------- *\
234 | * Form styling
235 | * -------------------------------------------------------------------------- */
236 |
237 | form {
238 | margin-top: 2em;
239 | }
240 |
241 | form label {
242 | font-weight: bold;
243 | margin: 5px 10px 5px 0;
244 | display: block;
245 | }
246 |
247 | form input {
248 | font: inherit;
249 | padding: 5px;
250 | border: 1px solid black;
251 | width: 100%;
252 | margin: 0;
253 | font-size: 1.3rem;
254 | }
255 |
256 | form button {
257 | background-color: #000;
258 | color: white;
259 | border: 0;
260 | font-family: inherit;
261 | font-size: inherit;
262 | padding: 8px 15px;
263 | cursor: pointer;
264 | transition: 0.15s;
265 | display: inline-block;
266 | border: 2px solid #000;
267 | }
268 |
269 | form button:hover,
270 | form button:active {
271 | background-color: #fff;
272 | color: #000;
273 | }
274 |
275 | .form__hint {
276 | margin: 5px 0 0 0;
277 | font-size: 1em;
278 | }
279 |
280 | .form__element {
281 | margin-bottom: 20px;
282 | }
--------------------------------------------------------------------------------
/network-based-img-loading/img/test.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/network-based-img-loading/img/test.jpg
--------------------------------------------------------------------------------
/network-based-img-loading/img/test_high.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/network-based-img-loading/img/test_high.jpg
--------------------------------------------------------------------------------
/network-based-img-loading/img/test_low.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/network-based-img-loading/img/test_low.jpg
--------------------------------------------------------------------------------
/network-based-img-loading/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Network based image loading using the Network Information API
9 |
43 |
44 |
45 |
46 | Network based image loading using the Network Information API
47 |
48 |
49 |
50 |
51 |
52 |
53 |
89 |
90 |
91 |
--------------------------------------------------------------------------------
/network-based-img-loading/sw.js:
--------------------------------------------------------------------------------
1 | const VERSION = '0.1';
2 |
3 | self.addEventListener('install', function(event) {
4 | console.log(`Service Worker installed`);
5 | return self.skipWaiting();
6 | });
7 |
8 | self.addEventListener('activate', function(event) {
9 | console.log(`Service Worker activated`);
10 | return self.clients.claim();
11 | });
12 |
13 | const navigatorConnectionSupported = 'connection' in navigator && navigator.connection.effectiveType;
14 |
15 | self.addEventListener('fetch', function(event) {
16 | console.log(`fetch for request ${event.request.url}`);
17 | // check if the request is an image
18 | if (/\.jpg$|.png$|.webp$/.test(event.request.url)) {
19 |
20 | // check if navigator.connection is supported
21 | if (navigatorConnectionSupported) {
22 | const connectionEffectiveType = navigator.connection.effectiveType;
23 |
24 | // check if effectiveType is supported
25 | if (connectionEffectiveType) {
26 | const req = event.request.clone();
27 | let imageResolution = '';
28 |
29 | switch (connectionEffectiveType) {
30 | case "slow-2g":
31 | case "2g":
32 | imageResolution = '_low';
33 | break;
34 | case "4g":
35 | imageResolution = '_high';
36 | break;
37 | default:
38 | imageResolution = '';
39 | }
40 |
41 | // Build the image we want to return based on connection
42 | const returnUrl = req.url.substr(0, req.url.lastIndexOf(".")) + imageResolution + "." + req.url.split('.').pop();
43 |
44 | event.respondWith(
45 | fetch(returnUrl, {
46 | mode: 'no-cors'
47 | })
48 | );
49 |
50 | }
51 |
52 | }
53 | }
54 | });
--------------------------------------------------------------------------------
/push-notifications/.env.example:
--------------------------------------------------------------------------------
1 | MONGODB_URI=mongodb://localhost/push-notifications
2 | GCM_KEY=asdf123
3 | VAPID_PRIVATE_KEY=asdf123
4 | VAPID_PUBLIC_KEY=asdf123
5 |
6 |
--------------------------------------------------------------------------------
/push-notifications/.gitignore:
--------------------------------------------------------------------------------
1 | ### Node ###
2 | # Logs
3 | logs
4 | *.log
5 | npm-debug.log*
6 |
7 | # Dependency directories
8 | node_modules
9 |
10 | # dotenv environment variables file
11 | .env
12 | .env.production
13 |
--------------------------------------------------------------------------------
/push-notifications/README.md:
--------------------------------------------------------------------------------
1 | Demo of using push notifications
2 | ============
3 |
4 | [Demo](https://justmarkup.com/demos/toggle-content/showmore/with-target)
5 |
6 | This demo shows how to implement push notifications on the front-end and on the back-end (using the [Web Push library for Node.js](https://github.com/web-push-libs/web-push))
7 |
--------------------------------------------------------------------------------
/push-notifications/controllers/home.js:
--------------------------------------------------------------------------------
1 | /**
2 | * GET /
3 | * Home page.
4 | */
5 | exports.index = (req, res) => {
6 | res.render('home');
7 | };
8 |
--------------------------------------------------------------------------------
/push-notifications/controllers/push.js:
--------------------------------------------------------------------------------
1 | const Push = require('../models/Push');
2 | const webPush = require('web-push');
3 | /**
4 | * POST /
5 | * Subscribe user.
6 | */
7 | exports.subscribe = (req, res) => {
8 |
9 | const endpoint = req.body;
10 |
11 | const push = new Push({
12 | endpoint: endpoint.endpoint,
13 | keys: {
14 | p256dh: endpoint.keys.p256dh,
15 | auth: endpoint.keys.auth
16 | }
17 | });
18 |
19 | push.save(function (err, push) {
20 | if (err) {
21 | console.error('error with subscribe', error);
22 | res.status(500).send('subscription not possible');
23 | return;
24 | }
25 |
26 | const payload = JSON.stringify({
27 | title: 'Welcome',
28 | body: 'Thank you for enabling push notifications',
29 | icon: '/android-chrome-192x192.png'
30 | });
31 |
32 | const options = {
33 | TTL: 86400
34 | };
35 |
36 | const subscription = {
37 | endpoint: push.endpoint,
38 | keys: {
39 | p256dh: push.keys.p256dh,
40 | auth: push.keys.auth
41 | }
42 | };
43 |
44 | webPush.sendNotification(
45 | subscription,
46 | payload,
47 | options
48 | ).then(function() {
49 | console.log("Send welcome push notification");
50 | }).catch(err => {
51 | console.error("Unable to send welcome push notification", err );
52 | });
53 | res.status(200).send('subscribe');
54 | return;
55 | });
56 | };
57 |
58 | /**
59 | * POST /
60 | * Unsubscribe user.
61 | */
62 | exports.unsubscribe = (req, res) => {
63 |
64 | const endpoint = req.body.endpoint;
65 |
66 | Push.findOneAndRemove({endpoint: endpoint}, function (err,data){
67 | if(err) {
68 | console.error('error with unsubscribe', error);
69 | res.status(500).send('unsubscription not possible');
70 | }
71 | console.log('unsubscribed');
72 | res.status(200).send('unsubscribe');
73 | });
74 | };
75 |
--------------------------------------------------------------------------------
/push-notifications/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('dotenv').config();
4 | const express = require("express");
5 | const app = express();
6 | const path = require('path');
7 | const bodyParser = require('body-parser');
8 | const mongoose = require('mongoose');
9 | const webPush = require('web-push');
10 |
11 |
12 | /**
13 | * Set API key
14 | */
15 | webPush.setVapidDetails(
16 | 'mailto:hallo@justmarkup.com',
17 | process.env.VAPID_PUBLIC_KEY,
18 | process.env.VAPID_PRIVATE_KEY
19 | );
20 |
21 | /**
22 | * Controllers (route handlers).
23 | */
24 | const homeController = require('./controllers/home');
25 | const pushController = require('./controllers/push');
26 |
27 | /**
28 | * Connect to MongoDB.
29 | */
30 | mongoose.Promise = global.Promise;
31 | mongoose.connect(process.env.MONGODB_URI);
32 | mongoose.connection.on('error', () => {
33 | console.log('MongoDB connection error. Please make sure MongoDB is running.');
34 | process.exit();
35 | });
36 |
37 | /**
38 | * Express configuration.
39 | */
40 | app.set('views', path.join(__dirname, '/views'));
41 | app.set('view engine', 'pug');
42 |
43 | app.use("/", express.static(__dirname + '/public'));
44 |
45 | app.use(bodyParser.urlencoded({ extended: true }));
46 | app.use(bodyParser.json());
47 |
48 | /**
49 | * Primary app routes.
50 | */
51 | app.get('/', homeController.index);
52 | app.post('/push/subscribe', pushController.subscribe);
53 | app.post('/push/unsubscribe', pushController.unsubscribe);
54 |
55 | app.listen(process.env.PORT || 3000);
56 |
--------------------------------------------------------------------------------
/push-notifications/models/Push.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const Schema = mongoose.Schema;
3 |
4 | var pushSchema = new Schema({
5 | endpoint: String,
6 | keys: {
7 | p256dh: String,
8 | auth: String
9 | }
10 | });
11 |
12 | const Push = mongoose.model('Push', pushSchema);
13 |
14 | module.exports = Push;
15 |
--------------------------------------------------------------------------------
/push-notifications/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "push-notifications",
3 | "version": "1.0.0",
4 | "description": "Demo of using push notifications on the fron-end and back-end",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "start": "node index.js"
9 | },
10 | "author": "Michael Scharnagl @justmarkup",
11 | "license": "ISC",
12 | "dependencies": {
13 | "body-parser": "^1.16.0",
14 | "dotenv": "^4.0.0",
15 | "express": "^4.14.1",
16 | "mongoose": "^5.8.11",
17 | "path": "^0.12.7",
18 | "pug": "^2.0.0-beta11",
19 | "web-push": "^3.2.1"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/push-notifications/public/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/push-notifications/public/android-chrome-192x192.png
--------------------------------------------------------------------------------
/push-notifications/public/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/push-notifications/public/android-chrome-512x512.png
--------------------------------------------------------------------------------
/push-notifications/public/css/main.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | }
6 |
7 | body {
8 | font-family: Helvetica, Arial, sans-serif;
9 | line-height: 1.3;
10 | color: #111;
11 | width: 900px;
12 | max-width: 90%;
13 | max-width: calc(100vw - 40px);
14 | margin: 0 auto;
15 | }
16 |
17 | .message {
18 | position: fixed;
19 | top: 0;
20 | left: 0;
21 | width: 100%;
22 | padding: 20px;
23 | margin: 0;
24 | background: #eee;
25 | }
26 |
27 | .message--error {
28 | background: #DF0000FF;
29 | color: #fff;
30 | }
31 |
32 | .hidden {
33 | display: none;
34 | }
35 |
--------------------------------------------------------------------------------
/push-notifications/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Push notifications demo",
3 | "short_name": "Push demo",
4 | "description": "Demo showing the push notification API",
5 | "icons": [
6 | {
7 | "src": "/android-chrome-192x192.png",
8 | "sizes": "192x192",
9 | "type": "image/png"
10 | },
11 | {
12 | "src": "/android-chrome-512x512.png",
13 | "sizes": "512x512",
14 | "type": "image/png"
15 | }
16 | ],
17 | "start_url": "./?homescreen=1",
18 | "display": "standalone",
19 | "background_color": "#000",
20 | "theme_color": "#000",
21 | "gcm_sender_id": "38399124687",
22 | "gcm_user_visible_only": true
23 | }
24 |
--------------------------------------------------------------------------------
/push-notifications/public/script/push.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const appServerKey = 'BHLCrsFGJQIVgg-XNp8F59C8UFF49GAVxvYMvyCURim3nMYI5TMdsOcrh-yJM7KbtZ3psi5FhfvaJbU_11jwtPY';
4 |
5 | const pushWrapper = document.querySelector('.push-wrapper');
6 | const pushButton = document.querySelector('.push-button');
7 |
8 | let hasSubscription = false;
9 | let serviceWorkerRegistration = null;
10 | let subscriptionData = false;
11 |
12 | function urlB64ToUint8Array(base64String) {
13 | const padding = '='.repeat((4 - base64String.length % 4) % 4);
14 | const base64 = (base64String + padding)
15 | .replace(/\-/g, '+')
16 | .replace(/_/g, '/');
17 |
18 | const rawData = window.atob(base64);
19 | const outputArray = new Uint8Array(rawData.length);
20 |
21 | for (let i = 0; i < rawData.length; ++i) {
22 | outputArray[i] = rawData.charCodeAt(i);
23 | }
24 | return outputArray;
25 | }
26 |
27 | function updatePushButton() {
28 | pushWrapper.classList.remove('hidden');
29 |
30 | if (hasSubscription) {
31 | pushButton.textContent = `Disable Push Notifications`;
32 | } else {
33 | pushButton.textContent = `Enable Push Notifications`;
34 | }
35 | }
36 |
37 | function subscribeUser() {
38 | serviceWorkerRegistration.pushManager.subscribe({
39 | userVisibleOnly: true,
40 | applicationServerKey: urlB64ToUint8Array(appServerKey)
41 | })
42 | .then(function(subscription) {
43 |
44 | fetch('/push/subscribe',{
45 | method: "POST",
46 | headers: {
47 | 'Content-Type': 'application/json'
48 | },
49 | body: JSON.stringify(subscription)
50 | })
51 | .then(function(response) {
52 | return response;
53 | })
54 | .then(function(text) {
55 | console.log('User is subscribed.');
56 | hasSubscription = true;
57 |
58 | updatePushButton();
59 | })
60 | .catch(function(error) {
61 | hasSubscription = false;
62 | console.error('error fetching subscribe', error);
63 | });
64 |
65 | })
66 | .catch(function(err) {
67 | console.log('Failed to subscribe the user: ', err);
68 | });
69 | }
70 |
71 | function unsubscribeUser() {
72 | serviceWorkerRegistration.pushManager.getSubscription()
73 | .then(function(subscription) {
74 | if (subscription) {
75 | subscriptionData = {
76 | endpoint: subscription.endpoint
77 | };
78 |
79 | fetch('/push/unsubscribe',{
80 | method: "POST",
81 | headers: {
82 | 'Content-Type': 'application/json'
83 | },
84 | body: JSON.stringify(subscriptionData)
85 | })
86 | .then(function(response) {
87 | return response;
88 | })
89 | .then(function(text) {
90 | hasSubscription = false;
91 |
92 | updatePushButton();
93 | })
94 | .catch(function(error) {
95 | hasSubscription = true;
96 | console.error('error fetching subscribe', error);
97 | });
98 |
99 | hasSubscription = false;
100 |
101 | updatePushButton();
102 | return subscription.unsubscribe();
103 | }
104 | });
105 | }
106 |
107 | function initPush() {
108 |
109 | pushButton.addEventListener('click', function() {
110 | if (hasSubscription) {
111 | unsubscribeUser();
112 | } else {
113 | subscribeUser();
114 | }
115 | });
116 |
117 | // Set the initial subscription value
118 | serviceWorkerRegistration.pushManager.getSubscription()
119 | .then(function(subscription) {
120 | hasSubscription = !(subscription === null);
121 |
122 | updatePushButton();
123 | });
124 | }
125 |
126 | navigator.serviceWorker.register('sw.js')
127 | .then(function(sw) {
128 | serviceWorkerRegistration = sw;
129 | initPush();
130 | })
131 | .catch(function(error) {
132 | console.error('Service Worker Error', error);
133 | });
134 |
135 |
--------------------------------------------------------------------------------
/push-notifications/public/sw.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const VERSION = 'v0.0.5';
4 |
5 | self.addEventListener('install', function(event) {
6 | self.skipWaiting();
7 | event.waitUntil(
8 | caches.open(VERSION).then(function(cache) {
9 | return cache.addAll([
10 | '/',
11 | '/manifest.json',
12 | '/css/main.css',
13 | '/script/push.js'
14 | ]);
15 | })
16 | );
17 | });
18 |
19 | self.addEventListener('fetch', function(event) {
20 | let request = event.request;
21 |
22 | if (request.method !== 'GET') return;
23 |
24 | event.respondWith(
25 | caches.match(request).then(function(response) {
26 | return response || fetch(request);
27 | }).catch(function() {
28 | return caches.match('/');
29 | })
30 | );
31 | });
32 |
33 | self.addEventListener('activate', function(event) {
34 | if (self.clients && clients.claim) {
35 | clients.claim();
36 | }
37 | event.waitUntil(
38 | caches
39 | .keys()
40 | .then(function (keys) {
41 | return Promise.all(
42 | keys
43 | .filter(function (key) {
44 | return !key.startsWith(VERSION);
45 | })
46 | .map(function (key) {
47 | return caches.delete(key);
48 | })
49 | );
50 | })
51 | .then(function() {
52 | console.log('new service worker version registered', VERSION);
53 | }).catch(function (error) {
54 | console.error('error registering new service worker version', error);
55 | })
56 | );
57 | });
58 |
59 | self.addEventListener('push', function(event) {
60 |
61 | let notificationData = {};
62 |
63 | try {
64 | notificationData = event.data.json();
65 | } catch (e) {
66 | notificationData = {
67 | title: 'Default title',
68 | body: 'Default message',
69 | icon: '/default-icon.png'
70 | };
71 | }
72 |
73 | event.waitUntil(
74 | self.registration.showNotification(notificationData.title, {
75 | body: notificationData.body,
76 | icon: notificationData.icon
77 | })
78 | );
79 |
80 | });
81 |
82 | self.addEventListener('notificationclick', function(event) {
83 |
84 | // close the notification
85 | event.notification.close();
86 |
87 | // see if the current is open and if it is focus it
88 | // otherwise open new tab
89 | event.waitUntil(
90 |
91 | self.clients.matchAll().then(function(clientList) {
92 |
93 | if (clientList.length > 0) {
94 | return clientList[0].focus();
95 | }
96 |
97 | return self.clients.openWindow('/');
98 | })
99 | );
100 | });
101 |
--------------------------------------------------------------------------------
/push-notifications/views/home.pug:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1 Demo showing push notifications
5 |
6 | article
7 | p If your browser supports push notification you will see a button "Enable Push Notifications". After clicking it and everything worked as expected you will get a push notification with the title "Welcome".
8 |
9 | p You can find the code on
10 | |
11 | a(href='https://github.com/justmarkup/demos/tree/gh-pages/push-notifications') Github
12 | | .
13 |
14 | div(class='push-wrapper hidden')
15 | button(class='push-button')
16 |
17 |
--------------------------------------------------------------------------------
/push-notifications/views/layout.pug:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | meta(charset='utf-8')
5 | link(rel='manifest', href='/manifest.json')
6 | meta(name='viewport', content='width=device-width, initial-scale=1.0')
7 | meta(name='theme-color' content='#FFFFFF')
8 |
9 | title Demo showing push notifications
10 |
11 | link(rel='stylesheet', href='/css/main.css')
12 |
13 | body
14 | include partials/header
15 |
16 | main
17 | block content
18 |
19 | include partials/footer
20 |
21 | script.
22 | // load JavaScript for push notifications in supported browsers
23 | if ('serviceWorker' in navigator && 'PushManager' in window) {
24 | function loadScript(src) {
25 | return new Promise(function (resolve, reject) {
26 | var s;
27 | s = document.createElement('script');
28 | s.src = src;
29 | s.onload = resolve;
30 | s.onerror = reject;
31 | document.head.appendChild(s);
32 | });
33 | }
34 |
35 | loadScript('/script/push.js');
36 | } else {
37 | var info;
38 | info = document.createElement('p');
39 | info.className = 'message message--error';
40 | info.textContent = "Sorry, your browser doesn't support push notifications";
41 | document.body.appendChild(info);
42 | }
43 |
--------------------------------------------------------------------------------
/push-notifications/views/partials/footer.pug:
--------------------------------------------------------------------------------
1 | footer
2 |
3 |
--------------------------------------------------------------------------------
/push-notifications/views/partials/header.pug:
--------------------------------------------------------------------------------
1 | header
2 |
3 |
--------------------------------------------------------------------------------
/responsive-pop-out-menu/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Responsive Pop-out Menu
7 |
8 |
9 |
10 |
27 |
28 | Responsive Pop-out Menu
29 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus velit cum dolor, odio consectetur, sit. Autem unde pariatur, tempora facilis libero. Corporis nemo ducimus minus laborum tempore architecto necessitatibus, quis.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque ducimus laborum id suscipit sint pariatur commodi debitis unde voluptas laboriosam sequi porro voluptatibus, distinctio quasi neque quae ipsa, illum mollitia!Lorem ipsum dolor sit amet, consectetur adipisicing elit. Unde earum natus tempora eum, molestias eaque. Est minima consequatur, veniam ullam minus magnam qui quibusdam a, iusto eaque assumenda ad, consequuntur?Lorem ipsum dolor sit amet, consectetur adipisicing elit. In, fuga! Labore quidem adipisci commodi et, porro dicta distinctio aut dolorum rerum harum reiciendis voluptate minima soluta numquam voluptatum molestias deleniti.
30 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Maiores facilis ipsam dignissimos illum, tenetur neque nostrum temporibus, delectus deleniti fugit nulla magni nam animi facere, quia explicabo quasi alias magnam!Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolor eligendi aliquid architecto alias ut vero reprehenderit, blanditiis sint laudantium voluptatem, qui minus culpa placeat quis doloremque. Cum unde sapiente obcaecati!
31 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Doloribus velit cum dolor, odio consectetur, sit. Autem unde pariatur, tempora facilis libero. Corporis nemo ducimus minus laborum tempore architecto necessitatibus, quis.Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eaque ducimus laborum id suscipit sint pariatur commodi debitis unde voluptas laboriosam sequi porro voluptatibus, distinctio quasi neque quae ipsa, illum mollitia!Lorem ipsum dolor sit amet, consectetur adipisicing elit. Unde earum natus tempora eum, molestias eaque. Est minima consequatur, veniam ullam minus magnam qui quibusdam a, iusto eaque assumenda ad, consequuntur?Lorem ipsum dolor sit amet, consectetur adipisicing elit. In, fuga! Labore quidem adipisci commodi et, porro dicta distinctio aut dolorum rerum harum reiciendis voluptate minima soluta numquam voluptatum molestias deleniti.
32 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Maiores facilis ipsam dignissimos illum, tenetur neque nostrum temporibus, delectus deleniti fugit nulla magni nam animi facere, quia explicabo quasi alias magnam!Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolor eligendi aliquid architecto alias ut vero reprehenderit, blanditiis sint laudantium voluptatem, qui minus culpa placeat quis doloremque. Cum unde sapiente obcaecati!
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/responsive-pop-out-menu/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | background
6 |
7 |
8 |
9 |
10 |
11 |
12 | Layer 1
13 | Logo
14 |
15 |
--------------------------------------------------------------------------------
/responsive-pop-out-menu/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Responsive Pop-out Menu
3 | * https://github.com/justmarkup/demos/responsive-pop-out-menu
4 | * Copyright (c) 2015 @justmarkup
5 | * Available under the MIT license
6 | */
7 |
8 | (function (doc) {
9 | // CTM, only init JavaScript for modern browsers
10 | if ('visibilityState' in doc) {
11 |
12 | // Add class to html elment once js is available
13 | doc.documentElement.className = 'js';
14 |
15 | var menuLayer = doc.getElementById('is--menu-layer'),
16 | menuClose = doc.getElementById('is--menu-close'),
17 | menuOpen = doc.getElementById('is--menu-open');
18 |
19 | function openMenu () {
20 | menuLayer.className = 'is--menu-opened';
21 | }
22 |
23 | function closeMenu () {
24 | menuLayer.className = '';
25 | }
26 |
27 | menuClose.addEventListener("click", closeMenu, false);
28 | menuOpen.addEventListener("click", openMenu, false);
29 | }
30 | }(document));
--------------------------------------------------------------------------------
/responsive-pop-out-menu/style.css:
--------------------------------------------------------------------------------
1 | /*! Responsive Pop-out Menu by @justmarkup */
2 |
3 | /* basic styles for the demo */
4 | html {
5 | margin: 0;
6 | padding: 0;
7 | font-size: 62.5%;
8 | }
9 |
10 | body {
11 | font-family: Arial, sans-serif;
12 | margin: 0;
13 | padding: 0;
14 | }
15 |
16 | .header {background-color: #E65100;}
17 |
18 | .main {
19 | width: 900px;
20 | max-width: 90%;
21 | margin: 20px auto;
22 | }
23 |
24 | h1 {
25 | margin: 0;
26 | padding: 0;
27 | }
28 |
29 | h2 {
30 | font-size: 4em;
31 | font-weight: normal;
32 | }
33 |
34 | p {
35 | font-size: 2em;
36 | line-height: 1.5;
37 | color: #000;
38 | }
39 |
40 | .logo a {
41 | display: block;
42 | text-indent: -2015em;
43 | background: url(logo.svg) no-repeat center center;
44 | width: 180px;
45 | height: 80px;
46 | }
47 |
48 | /* styles for navigation for big screens and devices without js */
49 | .navigation ul {
50 | margin: 0 auto;
51 | padding: 0;
52 | list-style: none;
53 | display: table;
54 | }
55 |
56 | .navigation li {
57 | display: inline;
58 | }
59 |
60 | .navigation li a {
61 | display: inline-block;
62 | text-decoration: none;
63 | color: #fff;
64 | font-size: 20px;
65 | padding: 10px 20px;
66 | }
67 |
68 | .menu-toggle {
69 | display: none;
70 | }
71 |
72 | /* styles for navigation for small screens and with js */
73 | @media all and (max-width: 600px) {
74 | .js .header {
75 | height: 80px;
76 | }
77 |
78 | .js .menu-toggle {
79 | display: block;
80 | color: #fff;
81 | float: right;
82 | margin: 30px 20px;
83 | text-decoration: none;
84 | padding: 5px;
85 | border: 1px solid #fff;
86 | font-size: 1.6em;
87 | }
88 | .js .menu-toggle-close {
89 | display: none;
90 | transition: all 1000ms;
91 | }
92 | .js .logo {float: left;}
93 | .js .navigation div {
94 | background-color: #E65100;
95 | position: fixed;
96 | top: 30px;
97 | right: 20px;
98 | width: 50px;
99 | height: 20px;
100 | overflow: hidden;
101 | opacity: 0;
102 | transition: all 500ms;
103 | z-index: -1;
104 | }
105 | .js .navigation .is--menu-opened {
106 | opacity: 1;
107 | height: 100%;
108 | width: 100%;
109 | right: 0;
110 | top: 0;
111 | z-index: 1;
112 | }
113 | .js .is--menu-opened .menu-toggle-close {
114 | display: block;
115 | }
116 | .js .navigation ul {
117 | display: block;
118 | position: absolute;
119 | bottom: 10px;
120 | right: 0;
121 | left: 0;
122 | -webkit-transform: rotate(180deg);
123 | -ms-transform: rotate(180deg);
124 | transform: rotate(180deg);
125 | }
126 | .js .navigation li{
127 | display: block;
128 | -webkit-transform: rotate(-180deg);
129 | -ms-transform: rotate(-180deg);
130 | transform: rotate(-180deg);
131 | }
132 | .js .navigation li a {
133 | display: block;
134 | border-top: 1px solid #fff;
135 | text-align: right;
136 | }
137 | }
--------------------------------------------------------------------------------
/theme-changer-css-custom-properties/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Theme changer with custom variables
7 |
41 |
42 |
43 |
44 | Choose theme
45 |
46 | default
47 | light
48 | dark
49 |
50 |
51 | Headline
52 | Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus vel accusamus, reiciendis dolorum qui aut quasi cum mollitia cupiditate vitae culpa deserunt ducimus tempora libero quae voluptates necessitatibus, tenetur veritatis. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Possimus vel accusamus, reiciendis dolorum qui aut quasi cum mollitia cupiditate vitae culpa deserunt ducimus tempora libero quae voluptates necessitatibus, tenetur veritatis.
53 |
54 | Read the full article .
55 |
122 |
123 |
124 |
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/web-share-target-image-to-grayscale/android-chrome-192x192.png
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/web-share-target-image-to-grayscale/android-chrome-512x512.png
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/web-share-target-image-to-grayscale/apple-touch-icon.png
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/web-share-target-image-to-grayscale/favicon-16x16.png
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/web-share-target-image-to-grayscale/favicon-32x32.png
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/web-share-target-image-to-grayscale/favicon.ico
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Image to Grayscale
9 |
10 |
11 |
12 |
13 |
14 |
15 |
40 |
41 |
42 |
43 |
44 | Image to Grayscale
45 | Install this page to share any image via your native Gallery and get a grayscale version back.
46 | Note: Web Share Target API is currently only supported in new Chromium-based Browsers, so you need to use one of them to try it.
47 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Image to Grayscale",
3 | "short_name": "Image to Grayscale",
4 | "start_url": "./index.html",
5 | "theme_color": "#333",
6 | "background_color": "#333",
7 | "icons": [{
8 | "src": "./android-chrome-192x192.png",
9 | "sizes": "192x192",
10 | "type": "image/png"
11 | },
12 | {
13 | "src": "./android-chrome-512x512.png",
14 | "sizes": "512x512",
15 | "type": "image/png"
16 | }
17 | ],
18 | "share_target": {
19 | "action": "./upload",
20 | "method": "POST",
21 | "enctype": "multipart/form-data",
22 | "params": {
23 | "files": [{
24 | "name": "file",
25 | "accept": ["image/*"]
26 | }]
27 | }
28 | },
29 | "display": "minimal-ui",
30 | "scope": "./"
31 | }
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justmarkup/demos/bfcb69376d11125b696f6b06f31cd5530471a947/web-share-target-image-to-grayscale/mstile-150x150.png
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/output.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Image to Grayscale
9 |
10 |
11 |
12 |
13 |
14 |
15 |
45 |
46 |
47 |
48 |
49 | Image to Grayscale
50 |
51 |
52 |
84 |
85 |
86 |
--------------------------------------------------------------------------------
/web-share-target-image-to-grayscale/sw.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | const version = '1.0';
4 |
5 | addEventListener('install', event => {
6 | skipWaiting();
7 | });
8 |
9 | addEventListener('activate', event => {
10 | clients.claim();
11 | });
12 |
13 | addEventListener('fetch', event => {
14 | if (event.request.method !== 'POST') {
15 | return;
16 | }
17 |
18 | if (event.request.url.startsWith('https://justmarkup.com/demos/web-share-target-image-to-grayscale/upload') === false) {
19 | return;
20 | }
21 |
22 | event.respondWith(Response.redirect('https://justmarkup.com/demos/web-share-target-image-to-grayscale/output.html'));
23 | event.waitUntil(async function() {
24 | const data = await event.request.formData();
25 | const client = await self.clients.get(event.resultingClientId || event.clientId);
26 |
27 | const file = data.get('file');
28 | client.postMessage({ file, action: 'load-image' });
29 | }());
30 | });
--------------------------------------------------------------------------------
Commenty Commentato
18 |First comment
19 |