├── LICENSE.md
├── README.md
├── _locales
└── en
│ └── messages.json
├── docs
├── screenshot-active.png
└── screenshot.png
├── icons
├── icon128.png
├── icon16.png
├── icon19.png
├── icon24.png
├── icon32.png
├── icon48.png
└── icon96.png
├── manifest.json
└── src
├── bg
└── background.js
├── inject
├── birdview.css
├── birdview.js
└── inject.js
├── options.html
└── options.js
/LICENSE.md:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2017, Jess Telford
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 | PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Birdview
2 |
3 | > Chrome Extension that gives you a glance at a whole web page with an aerial view
4 |
5 | **[Install in Chrome](https://chrome.google.com/webstore/detail/birdview/mkkcfebffmojpmhnnhjiocmhiidjogge)**
6 |
7 | _Based on [Achraf Kassioui](https://twitter.com/achrafkassioui)'s [Birdview.js](http://achrafkassioui.com/birdview)._
8 |
9 | 
10 |
11 | 
12 |
--------------------------------------------------------------------------------
/_locales/en/messages.json:
--------------------------------------------------------------------------------
1 | {
2 | "l10nTabName": {
3 | "message":"Localization"
4 | ,"description":"name of the localization tab"
5 | }
6 | ,"l10nHeader": {
7 | "message":"It does localization too! (this whole tab is, actually)"
8 | ,"description":"Header text for the localization section"
9 | }
10 | ,"l10nIntro": {
11 | "message":"'L10n' refers to 'Localization' - 'L' an 'n' are obvious, and 10 comes from the number of letters between those two. It is the process/whatever of displaying something in the language of choice. It uses 'I18n', 'Internationalization', which refers to the tools / framework supporting L10n. I.e., something is internationalized if it has I18n support, and can be localized. Something is localized for you if it is in your language / dialect."
12 | ,"description":"introduce the basic idea."
13 | }
14 | ,"l10nProd": {
15 | "message":"You are planning to allow localization, right? You have no idea who will be using your extension! You have no idea who will be translating it! At least support the basics, it's not hard, and having the framework in place will let you transition much more easily later on."
16 | ,"description":"drive the point home. It's good for you."
17 | }
18 | ,"l10nFirstParagraph": {
19 | "message":"When the options page loads, elements decorated with data-l10n will automatically be localized!"
20 | ,"description":"inform that elements will be localized on load"
21 | }
22 | ,"l10nSecondParagraph": {
23 | "message":"If you need more complex localization, you can also define data-l10n-args . This should contain $containerType$ filled with $dataType$ , which will be passed into Chrome's i18n API as $functionArgs$ . In fact, this paragraph does just that, and wraps the args in mono-space font. Easy!"
24 | ,"description":"introduce the data-l10n-args attribute. End on a lame note."
25 | ,"placeholders": {
26 | "containerType": {
27 | "content":"$1"
28 | ,"example":"'array', 'list', or something similar"
29 | ,"description":"type of the args container"
30 | }
31 | ,"dataType": {
32 | "content":"$2"
33 | ,"example":"string"
34 | ,"description":"type of data in each array index"
35 | }
36 | ,"functionArgs": {
37 | "content":"$3"
38 | ,"example":"arguments"
39 | ,"description":"whatever you call what you pass into a function/method. args, params, etc."
40 | }
41 | }
42 | }
43 | ,"l10nThirdParagraph": {
44 | "message":"Message contents are passed right into innerHTML without processing - include any tags (or even scripts) that you feel like. If you have an input field, the placeholder will be set instead, and buttons will have the value attribute set."
45 | ,"description":"inform that we handle placeholders, buttons, and direct HTML input"
46 | }
47 | ,"l10nButtonsBefore": {
48 | "message":"Different types of buttons are handled as well. <button> elements have their html set:"
49 | }
50 | ,"l10nButton": {
51 | "message":"in a button "
52 | }
53 | ,"l10nButtonsBetween": {
54 | "message":"while <input type='submit'> and <input type='button'> get their 'value' set (note: no HTML):"
55 | }
56 | ,"l10nSubmit": {
57 | "message":"a submit value"
58 | }
59 | ,"l10nButtonsAfter": {
60 | "message":"Awesome, no?"
61 | }
62 | ,"l10nExtras": {
63 | "message":"You can even set data-l10n on things like the <title> tag, which lets you have translatable page titles, or fieldset <legend> tags, or anywhere else - the default Boil.localize() behavior will check every tag in the document, not just the body."
64 | ,"description":"inform about places which may not be obvious, like
, etc"
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/docs/screenshot-active.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/docs/screenshot-active.png
--------------------------------------------------------------------------------
/docs/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/docs/screenshot.png
--------------------------------------------------------------------------------
/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon128.png
--------------------------------------------------------------------------------
/icons/icon16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon16.png
--------------------------------------------------------------------------------
/icons/icon19.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon19.png
--------------------------------------------------------------------------------
/icons/icon24.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon24.png
--------------------------------------------------------------------------------
/icons/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon32.png
--------------------------------------------------------------------------------
/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon48.png
--------------------------------------------------------------------------------
/icons/icon96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jesstelford/birdview-chrome-extension/3f21dad9c1d5b4bad90326b110feffb4fdf33b1f/icons/icon96.png
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Birdview",
3 | "version": "1.2.0",
4 | "manifest_version": 2,
5 | "description": "Get a glance at a whole web page with an aerial view",
6 | "homepage_url": "http://achrafkassioui.com/birdview",
7 | "icons": {
8 | "16": "icons/icon16.png",
9 | "19": "icons/icon19.png",
10 | "24": "icons/icon24.png",
11 | "32": "icons/icon32.png",
12 | "48": "icons/icon48.png",
13 | "96": "icons/icon96.png",
14 | "128": "icons/icon128.png"
15 | },
16 | "default_locale": "en",
17 | "background": {
18 | "scripts": [
19 | "src/bg/background.js"
20 | ],
21 | "persistent": false
22 | },
23 | "options_ui": {
24 | "page": "src/options.html",
25 | "chrome_style": true
26 | },
27 | "browser_action": {
28 | "default_title": "browser action demo",
29 | "default_icon": {
30 | "16": "icons/icon16.png",
31 | "19": "icons/icon19.png",
32 | "24": "icons/icon24.png",
33 | "32": "icons/icon32.png",
34 | "48": "icons/icon48.png",
35 | "96": "icons/icon96.png",
36 | "128": "icons/icon96.png"
37 | }
38 | },
39 | "permissions": [
40 | "https://*/*",
41 | "http://*/*",
42 | "storage"
43 | ],
44 | "web_accessible_resources": [
45 | "src/inject/birdview.js",
46 | "src/inject/birdview.css"
47 | ],
48 | "content_scripts": [
49 | {
50 | "matches": [
51 | "https://*/*",
52 | "http://*/*"
53 | ],
54 | "js": [
55 | "src/inject/inject.js"
56 | ]
57 | }
58 | ]
59 | }
60 |
--------------------------------------------------------------------------------
/src/bg/background.js:
--------------------------------------------------------------------------------
1 | chrome.browserAction.onClicked.addListener(function(tab) {
2 | chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
3 | chrome.tabs.sendMessage(tabs[0].id, {action: 'icon-clicked'});
4 | });
5 | });
6 |
--------------------------------------------------------------------------------
/src/inject/birdview.css:
--------------------------------------------------------------------------------
1 | /****************************************************
2 | *
3 | * Birdview.css
4 | * 1.0
5 | *
6 | * www.achrafkassioui.com/birdview/
7 | *
8 | * 20 May 2017
9 | *
10 | ****************************************************/
11 |
12 | #birdview_debug{
13 | position: fixed;
14 | width: 100%;
15 | height: 100%;
16 | top: 0;
17 | left: 0;
18 | color: #fff;
19 | font-size: 24px;
20 | line-height: 2em;
21 | text-shadow: 0px 0px 5px #000;
22 | text-align: center;
23 | pointer-events: none;
24 | z-index: +9999;
25 | }
26 |
27 | /****************************************************
28 | *
29 | * Button
30 | *
31 | ****************************************************/
32 |
33 | #auto_generated_birdview_button{
34 | position: fixed;
35 | display: block;
36 | width: 60px;
37 | height: 60px;
38 | right: 8px;
39 | top: 8px;
40 | margin: 0;
41 | padding: 0;
42 | text-align: center;
43 | color: #fff;
44 | font-family: 'Futura-PT', Futura, Futura-Medium, "Futura Medium", "Century Gothic", CenturyGothic, "Apple Gothic", AppleGothic, "URW Gothic L", "Avant Garde", sans-serif;
45 | font-style: normal;
46 | font-weight: 500;
47 | font-size: 40px;
48 | line-height: 54px;
49 | border: none;
50 | border-radius: 4px;
51 | background: #01d1fb;
52 | background: rgba(1, 209, 251, 0.8);
53 | cursor: pointer;
54 | z-index: +9998;
55 | }
56 |
57 | #auto_generated_birdview_button:hover,
58 | #auto_generated_birdview_button:active,
59 | #auto_generated_birdview_button:focus{
60 | background: rgba(1, 209, 251, 1);
61 | outline: none;
62 | }
63 |
64 | #auto_generated_birdview_button::-moz-focus-inner{
65 | border: 0;
66 | }
67 |
68 | #auto_generated_birdview_button.hidden{
69 | pointer-events: none;
70 | opacity: 0;
71 | }
72 |
73 | /****************************************************
74 | *
75 | * Overlay
76 | *
77 | ****************************************************/
78 |
79 | #auto_generated_birdview_overlay{
80 | position: fixed;
81 | width: 100%;
82 | height: 100%;
83 | top: 0;
84 | left: 0;
85 | color: #fff;
86 | font-family: 'Futura-PT', Futura, Futura-Medium, "Futura Medium", "Century Gothic", CenturyGothic, "Apple Gothic", AppleGothic, "URW Gothic L", "Avant Garde", sans-serif;
87 | font-style: normal;
88 | font-size: 20px;
89 | background: rgba(0, 0, 0, 0.4);
90 | overflow: hidden;
91 | z-index: +1000;
92 | pointer-events: none;
93 | user-select: none;
94 | -moz-user-select: none;
95 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
96 | transition-property: opacity;
97 | transition-timing-function: ease;
98 | opacity: 0;
99 | }
100 |
101 | #auto_generated_birdview_overlay.show{
102 | pointer-events: auto;
103 | user-select: auto;
104 | -moz-user-select: auto;
105 | opacity: 1;
106 | touch-action: pinch-zoom;
107 | }
108 |
109 | /*
110 | *
111 | * Overlay title
112 | *
113 | */
114 | #auto_generated_birdview_overlay h1{
115 | display: block;
116 | max-width: 33%;
117 | margin: 8px 16px 0px;
118 | text-align: left;
119 | color: #fff;
120 | font-weight: 500;
121 | font-size: 48px;
122 | }
123 |
124 | #auto_generated_birdview_overlay.zooming h1{
125 | color: #01d1fb;
126 | }
127 |
128 | /*
129 | *
130 | * Breadcrumb navigation
131 | *
132 | */
133 | #auto_generated_birdview_overlay a{
134 | display: inline-block;
135 | vertical-align: top;
136 | max-width: 33%;
137 | padding: 2px 4px;
138 | white-space: nowrap;
139 | overflow: hidden;
140 | text-overflow: ellipsis;
141 | color: inherit;
142 | text-decoration: none;
143 | font-weight: 500;
144 | }
145 |
146 | #auto_generated_birdview_overlay a:first-of-type{
147 | margin-left: 14px;
148 | }
149 |
150 | #auto_generated_birdview_overlay a:hover{
151 | color: #fff;
152 | background: #01d1fb;
153 | }
154 |
155 | /*
156 | *
157 | * Close button
158 | *
159 | */
160 | #auto_generated_birdview_overlay button{
161 | position: absolute;
162 | width: 96px;
163 | height: 96px;
164 | right: 0;
165 | top: 0;
166 | margin: 0;
167 | padding: 0;
168 | color: transparent;
169 | border: none;
170 | background: transparent no-repeat center center url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTkxOTI4MTIyNkJGMTFFN0JFQ0M5RTU3MERGOTc4ODUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTkxOTI4MTMyNkJGMTFFN0JFQ0M5RTU3MERGOTc4ODUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFOTE5MjgxMDI2QkYxMUU3QkVDQzlFNTcwREY5Nzg4NSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFOTE5MjgxMTI2QkYxMUU3QkVDQzlFNTcwREY5Nzg4NSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PusLg80AAAlpSURBVHja7J15cBVF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');
171 | background-size: 40px;
172 | cursor: pointer;
173 | }
174 |
175 | #auto_generated_birdview_overlay button:hover{
176 | background-color: #01d1fb;
177 | }
178 |
179 | /*
180 | *
181 | * Help message
182 | *
183 | */
184 | #auto_generated_birdview_overlay span{
185 | position: fixed;
186 | left: 0;
187 | bottom: 0;
188 | padding: 16px;
189 | color: #eee;
190 | font-weight: 400;
191 | font-size: 16px;
192 | line-height: 1.4em;
193 | }
194 |
195 | /*
196 | *
197 | * Responsive
198 | *
199 | */
200 | @media screen and (max-width: 768px){
201 | #auto_generated_birdview_overlay a{
202 | max-width: 25%;
203 | font-size: 16px;
204 | }
205 | #auto_generated_birdview_overlay h1{
206 | margin: 4px 8px 0px;
207 | font-size: 32px;
208 | }
209 | #auto_generated_birdview_overlay a{
210 | max-width: 25%;
211 | }
212 | #auto_generated_birdview_overlay a:first-of-type{
213 | margin-left: 6px;
214 | }
215 | #auto_generated_birdview_overlay button{
216 | width: 72px;
217 | height: 72px;
218 | background-size: 32px;
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/src/inject/birdview.js:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////////////
2 | //
3 | // Birdview.js
4 | // 1.0
5 | //
6 | // www.achrafkassioui.com/birdview/
7 | //
8 | // Copyright (C) 2017 Achraf Kassioui
9 | //
10 | // This program is free software: you can redistribute it and/or modify
11 | // it under the terms of the GNU General Public License as published by
12 | // the Free Software Foundation, either version 3 of the License, or any
13 | // later version.
14 | //
15 | // This program is distributed in the hope that it will be useful,
16 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 | // GNU General Public License for more details.
19 | //
20 | // https://www.gnu.org/licenses/gpl-3.0.en.html
21 | //
22 | ////////////////////////////////////////////////////////////////////////
23 |
24 | (function(root, factory){
25 | if(typeof define === 'function' && define.amd){
26 | define(function(){
27 | root.birdview = factory();
28 | return root.birdview;
29 | });
30 | }else if(typeof exports === 'object'){
31 | module.exports = factory();
32 | }else{
33 | root.birdview = factory();
34 | }
35 | }(this, function() {
36 | 'use strict';
37 |
38 | document.addEventListener('birdview:init', function(event) {
39 | birdview.init(event.detail.options || {});
40 | });
41 |
42 | document.addEventListener('birdview:toggle', function() {
43 | birdview.toggle();
44 | });
45 |
46 | ////////////////////////////////////////////////////////////////////////
47 | //
48 | // Variables
49 | //
50 | ////////////////////////////////////////////////////////////////////////
51 |
52 | var birdview = {};
53 | var settings;
54 |
55 | var scaled = false;
56 |
57 | var html = document.documentElement;
58 | var body = document.body;
59 | var parent;
60 | var child;
61 |
62 | var birdview_button;
63 | var overlay;
64 | var debug;
65 |
66 | var document_height;
67 | var viewport_height;
68 | var scale_value;
69 |
70 | var css_transform_origin_Y = 0;
71 |
72 | var zoom_level;
73 | var reference_zoom_level;
74 |
75 | var touch = {
76 | startX: 0,
77 | startY: 0,
78 | startSpan: 0,
79 | count: 0
80 | }
81 |
82 | /*
83 | *
84 | * Keycodes that disable birdview. Most are scrolling related keys
85 | * left: 37, up: 38, right: 39, down: 40, spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36, esc: 27
86 | *
87 | */
88 | var scrolling_keys = {37: 1, 38: 1, 39: 1, 40: 1, 32: 1, 33: 1, 34: 1, 35: 1, 36: 1, 27: 1};
89 |
90 | /*
91 | *
92 | * For feature test
93 | *
94 | */
95 | var supports = !!body.addEventListener && !!body.style.transition && !!body.style.transform;
96 |
97 | ////////////////////////////////////////////////////////////////////////
98 | //
99 | // Default settings
100 | //
101 | ////////////////////////////////////////////////////////////////////////
102 |
103 | var defaults = {
104 | shortcut: 90,
105 | transition_speed: 0.3,
106 | transition_easing: 'ease',
107 | css_transform_origin_X: 50,
108 | create_button: false,
109 | create_overlay: true,
110 | callback_start: null,
111 | callback_end: null,
112 | debug: false
113 | }
114 |
115 | ////////////////////////////////////////////////////////////////////////
116 | //
117 | // DOM setup
118 | //
119 | ////////////////////////////////////////////////////////////////////////
120 |
121 | /*
122 | *
123 | * Will wrap all body content inside
124 | *
125 | *
126 | *
127 | * ...
128 | *
129 | *
130 | *
131 | */
132 | function setupDOM(){
133 | wrapAll(body, 'birdview_parent');
134 | wrapAll('birdview_parent', 'birdview_child');
135 | parent = document.getElementById('birdview_parent');
136 | child = document.getElementById('birdview_child');
137 | if(settings.create_button) createButton();
138 | if(settings.create_overlay) createOverlay();
139 | if(settings.debug) createDebug();
140 | }
141 |
142 | function restoreDOM(){
143 | unwrap('birdview_child');
144 | unwrap('birdview_parent');
145 | child = null;
146 | parent = null;
147 | removeButton();
148 | removeOverlay();
149 | removeDebug();
150 | }
151 |
152 | function createButton(){
153 | birdview_button = document.createElement('button');
154 | birdview_button.innerHTML = 'Z';
155 | birdview_button.id = 'auto_generated_birdview_button';
156 | birdview_button.classList.add('birdview_toggle');
157 | body.appendChild(birdview_button);
158 | }
159 |
160 | function removeButton(){
161 | var button = document.getElementById('auto_generated_birdview_button');
162 | if(button) button.parentNode.removeChild(button);
163 | }
164 |
165 | function createOverlay(){
166 | overlay = document.createElement('div');
167 | overlay.id = 'auto_generated_birdview_overlay';
168 | if(settings.transition_speed === 0) overlay.style.transitionDuration = '0s';
169 | else overlay.style.transitionDuration = '0.1s';
170 | body.appendChild(overlay);
171 | }
172 |
173 | function removeOverlay(){
174 | var overlay = document.getElementById('auto_generated_birdview_overlay');
175 | if(overlay) overlay.parentNode.removeChild(overlay);
176 | }
177 |
178 | /*
179 | *
180 | * Creates a div to show debug messages on touch devices. Used with function log(msg)
181 | *
182 | */
183 | function createDebug(){
184 | debug = document.createElement('div');
185 | debug.id = 'birdview_debug';
186 | debug.innerHTML = 'DEBUG';
187 | body.appendChild(debug);
188 | }
189 |
190 | function removeDebug(){
191 | var debug = document.getElementById('birdview_debug');
192 | if(debug) debug.parentNode.removeChild(debug);
193 | }
194 |
195 | ////////////////////////////////////////////////////////////////////////
196 | //
197 | // Measurements
198 | //
199 | ////////////////////////////////////////////////////////////////////////
200 |
201 | function updateMeasurements(){
202 | document_height = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight);
203 | viewport_height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
204 | scale_value = viewport_height / document_height;
205 | }
206 |
207 | /*
208 | *
209 | * Returns the Y transform origin according to scrolling position, viewport hight and document length
210 | *
211 | */
212 | function birdviewTransformOriginY(){
213 | return css_transform_origin_Y = ((window.pageYOffset + (viewport_height * 0.5)) / document_height) * 100;
214 | }
215 |
216 | /*
217 | *
218 | * Given a value 'x' in [a, b], output a value 'y' in [c, d]
219 | *
220 | */
221 | function linearTransform(x, a, b, c, d){
222 | var y = ((x - a) * (d - c)) / (b - a) + c;
223 | return y;
224 | }
225 |
226 | function compensateScale(){
227 | var compensate_scale = (linearTransform(css_transform_origin_Y, 0, 100, -1, 1)) * viewport_height * 0.5;
228 | return compensate_scale;
229 | }
230 |
231 | function diveTransformOrigin(click_Y_position){
232 | return css_transform_origin_Y = ((click_Y_position / viewport_height) * 100);
233 | }
234 |
235 | function diveScrollPosition(click_Y_position){
236 | var scroll_to = ((click_Y_position / viewport_height) * document_height) - ((click_Y_position / viewport_height) * viewport_height);
237 | return scroll_to;
238 | }
239 |
240 | function currentZoomLevel(){
241 | var current_zoom_level = screen.width / window.innerWidth;
242 | return current_zoom_level;
243 | }
244 |
245 | function distanceBetween(a,b){
246 | var dx = a.x - b.x;
247 | var dy = a.y - b.y;
248 | return Math.sqrt( dx*dx + dy*dy );
249 | }
250 |
251 | ////////////////////////////////////////////////////////////////////////
252 | //
253 | // CSS transformations
254 | //
255 | ////////////////////////////////////////////////////////////////////////
256 |
257 | function birdviewCSS(){
258 | updateMeasurements();
259 | parent.style.transition = 'transform ' + settings.transition_speed + 's ' + settings.transition_easing;
260 | child.style.transition = 'transform ' + settings.transition_speed + 's ' + settings.transition_easing;
261 | child.style.transformOrigin = settings.css_transform_origin_X + '% ' + birdviewTransformOriginY() + '%';
262 | child.style.transform = 'scale(' + scale_value + ')';
263 | parent.style.transform = 'translateY(' + compensateScale() + 'px)';
264 | }
265 |
266 | function diveCSS(click_Y_position){
267 | child.style.transformOrigin = settings.css_transform_origin_X + '% ' + diveTransformOrigin(click_Y_position) + '%';
268 | child.style.transform = 'scale(1)';
269 | parent.style.transitionDuration = '0s';
270 | parent.style.transform = 'translateY(0px)';
271 | }
272 |
273 | function removeBirdviewCSS(){
274 | child.style.transformOrigin = settings.css_transform_origin_X + '% ' + css_transform_origin_Y + '%';
275 | child.style.transform = 'scale(1)';
276 | parent.style.transform = 'translateY(0px)';
277 | }
278 |
279 | ////////////////////////////////////////////////////////////////////////
280 | //
281 | // Birdview methods
282 | //
283 | ////////////////////////////////////////////////////////////////////////
284 |
285 | birdview.toggle = function(){
286 | if(!scaled) enterBirdview();
287 | else exitBirdview();
288 | }
289 |
290 | function enterBirdview(){
291 | if(scaled) return;
292 | if(viewport_height >= document_height){
293 | console.log('Page already fits in viewport');
294 | return;
295 | }
296 | scaled = true;
297 | if(settings.create_overlay) toggleOverlay();
298 | birdviewCSS();
299 | if(settings.callback_start) settings.callback_start();
300 | }
301 |
302 | function exitBirdview(){
303 | if(!scaled) return;
304 | scaled = false;
305 | if(settings.create_overlay) toggleOverlay();
306 | removeBirdviewCSS();
307 | if(settings.callback_end) settings.callback_end();
308 | }
309 |
310 | function dive(click_Y_position){
311 | if(!scaled) return;
312 | scaled = false;
313 | if(settings.create_overlay) toggleOverlay();
314 | diveCSS(click_Y_position);
315 | window.scrollTo(0, diveScrollPosition(click_Y_position));
316 | if(settings.callback_end) settings.callback_end();
317 | }
318 |
319 | ////////////////////////////////////////////////////////////////////////
320 | //
321 | // User interface
322 | //
323 | ////////////////////////////////////////////////////////////////////////
324 |
325 | function toggleOverlay(){
326 | if(settings.transition_speed === 0){
327 | if(scaled) showMenu();
328 | else hideOverlay();
329 | }
330 | /*
331 | *
332 | * Handle overlay display with transitionend event
333 | *
334 | */
335 | else showLoading();
336 | }
337 |
338 | function showLoading(){
339 | overlay.classList.add('show');
340 | overlay.classList.add('zooming');
341 | overlay.innerHTML = 'Zooming... ';
342 | if(settings.create_button) birdview_button.classList.remove('hidden');
343 | }
344 |
345 | function showMenu(){
346 | if(overlay.classList.contains('zooming')) overlay.classList.remove('zooming');
347 | if(!overlay.classList.contains('show')) overlay.classList.add('show');
348 | overlay.innerHTML = 'Birdview X ' + addNavigation() + 'Click to dive Press Z or pinch to toggle birdview ';
349 | if(settings.create_button) birdview_button.classList.add('hidden');
350 | }
351 |
352 | function hideOverlay(){
353 | overlay.innerHTML = '';
354 | if(overlay.classList.contains('show')) overlay.classList.remove('show');
355 | if(settings.create_button) birdview_button.classList.remove('hidden');
356 | }
357 |
358 | function addNavigation(){
359 | var breadcrumb;
360 | if(location.pathname == "/") breadcrumb = 'Home ';
361 | else breadcrumb = 'Home /' + document.title + ' ';
362 | return breadcrumb;
363 | }
364 |
365 | ////////////////////////////////////////////////////////////////////////
366 | //
367 | // Events handlers
368 | //
369 | ////////////////////////////////////////////////////////////////////////
370 |
371 | function eventHandler(e){
372 |
373 | if(e.type === 'transitionend'){
374 | if(scaled) showMenu();
375 | else hideOverlay();
376 | }
377 |
378 | if(e.type === 'resize' && scaled) birdviewCSS();
379 |
380 | if(e.type === 'orientationchange') reference_zoom_level = currentZoomLevel();
381 |
382 | if(e.type === 'keydown'){
383 | var tag = e.target.tagName;
384 | if(e.keyCode == settings.shortcut && tag != 'INPUT' && tag != 'TEXTAREA' && tag != 'SELECT'){
385 | birdview.toggle();
386 | }else if(scrolling_keys[e.keyCode]){
387 | exitBirdview();
388 | }
389 | }
390 |
391 | if(e.type === 'click'){
392 | var target = e.target;
393 | if(target.classList.contains('birdview_toggle') || target.parentNode.classList.contains('birdview_toggle')){
394 | birdview.toggle();
395 | }else if(scaled){
396 | var tag = target.tagName;
397 | if(tag === 'A' || target.parentNode.tagName === 'A'){
398 | return;
399 | }else if(tag != 'H1' && tag != 'A' && tag != 'BUTTON'){
400 | dive(e.clientY);
401 | }else if(tag === 'H1'){
402 | birdview.toggle();
403 | }
404 | }
405 | }
406 |
407 | if(e.type === 'scroll' || e.type === 'mousewheel' || e.type === 'onwheel' || e.type === 'DOMMouseScroll' || e.type === 'onmousewheel'){
408 | exitBirdview();
409 | }
410 |
411 | if(e.type === 'mousedown' && e.which === 2){
412 | exitBirdview();
413 | }
414 | };
415 |
416 | function onTouchStart(e){
417 | /*
418 | *
419 | * The touch handling logic is inspired from reveal.js https://github.com/hakimel/reveal.js/blob/master/js/reveal.js
420 | *
421 | */
422 | touch.startX = e.touches[0].clientX;
423 | touch.startY = e.touches[0].clientY;
424 | touch.count = e.touches.length;
425 |
426 | /*
427 | *
428 | * If there are two touches we need to memorize the distance between those two points to detect pinching
429 | *
430 | */
431 | if(e.touches.length === 2){
432 | touch.startSpan = distanceBetween({
433 | x: e.touches[1].clientX,
434 | y: e.touches[1].clientY
435 | },{
436 | x: touch.startX,
437 | y: touch.startY
438 | });
439 | }
440 | }
441 |
442 | function onTouchMove(e){
443 | /*
444 | *
445 | * If in birdview, then disable touch scroll
446 | *
447 | */
448 | if(scaled) e.preventDefault();
449 |
450 | /*
451 | *
452 | * We want to trigger birdview with a pinch in, but we don't want to disable the pinch out zoom
453 | * Test the zoom level of the document relative to a reference value stored on first load. Proceed only if the page is not zoomed in
454 | *
455 | */
456 | zoom_level = currentZoomLevel();
457 | if(zoom_level != reference_zoom_level) return;
458 |
459 | /*
460 | *
461 | * If the touch started with two points and still has two active touches, test for the pinch gesture
462 | *
463 | */
464 | if(e.touches.length === 2 && touch.count === 2){
465 | /*
466 | *
467 | * The current distance in pixels between the two touch points
468 | *
469 | */
470 | var currentSpan = distanceBetween({
471 | x: e.touches[1].clientX,
472 | y: e.touches[1].clientY
473 | },{
474 | x: touch.startX,
475 | y: touch.startY
476 | });
477 |
478 | /*
479 | *
480 | * If user starts pinching in, disable default browser behavior
481 | *
482 | */
483 | if(currentSpan <= touch.startSpan){
484 | e.preventDefault();
485 | }
486 |
487 | /*
488 | *
489 | * If the span is larger than the desired amount, toggle birdview
490 | *
491 | */
492 | if(Math.abs( touch.startSpan - currentSpan ) > 30 ){
493 | if(currentSpan < touch.startSpan){
494 | enterBirdview();
495 | }else{
496 | /*
497 | *
498 | * In birdview and if the user pinches out, dive into the Y mid point between the two touches
499 | *
500 | */
501 | dive( (touch.startY + e.touches[1].clientY) * 0.5 );
502 | }
503 | }
504 | }
505 | }
506 |
507 | ////////////////////////////////////////////////////////////////////////
508 | //
509 | // Utility functions
510 | //
511 | ////////////////////////////////////////////////////////////////////////
512 |
513 | function extend(defaults, options) {
514 | var extended = {};
515 | var prop;
516 | for(prop in defaults){
517 | if (Object.prototype.hasOwnProperty.call(defaults, prop)){
518 | extended[prop] = defaults[prop];
519 | }
520 | }
521 | for(prop in options){
522 | if(Object.prototype.hasOwnProperty.call(options, prop)){
523 | extended[prop] = options[prop];
524 | }
525 | }
526 | return extended;
527 | }
528 |
529 | function wrapAll(parent, wrapper_id){
530 | if(parent != body) var parent = document.getElementById(parent);
531 | var wrapper = document.createElement('div');
532 | wrapper.id = wrapper_id;
533 | while (parent.firstChild) wrapper.appendChild(parent.firstChild);
534 | parent.appendChild(wrapper);
535 | }
536 |
537 | function unwrap(wrapper){
538 | var wrapper = document.getElementById(wrapper);
539 | var parent = wrapper.parentNode;
540 | while (wrapper.firstChild) parent.insertBefore(wrapper.firstChild, wrapper);
541 | parent.removeChild(wrapper);
542 | }
543 |
544 | function log(message){
545 | if(debug) debug.innerHTML = message;
546 | }
547 |
548 | ////////////////////////////////////////////////////////////////////////
549 | //
550 | // Initialize
551 | //
552 | ////////////////////////////////////////////////////////////////////////
553 |
554 | birdview.init = function(options){
555 |
556 | if(!!supports) return;
557 |
558 | birdview.destroy();
559 |
560 | settings = extend(defaults, options || {} );
561 |
562 | setupDOM();
563 |
564 | updateMeasurements();
565 |
566 | reference_zoom_level = currentZoomLevel();
567 |
568 | if(settings.transition_speed != 0) child.addEventListener("transitionend", eventHandler, false);
569 |
570 | /*
571 | *
572 | * Active event listeners. See: https://developers.google.com/web/updates/2017/01/scrolling-intervention
573 | *
574 | */
575 | if('ontouchstart' in window){
576 | document.addEventListener('touchstart', onTouchStart, {passive: false});
577 | document.addEventListener('touchmove', onTouchMove, {passive: false});
578 | }else{
579 | }
580 |
581 | document.addEventListener('keydown', eventHandler, false);
582 |
583 | document.addEventListener('click', eventHandler, false);
584 |
585 | window.addEventListener('scroll', eventHandler, false);
586 |
587 | window.addEventListener('resize', eventHandler, false);
588 |
589 | window.addEventListener("orientationchange", eventHandler, false);
590 |
591 | console.log('Birdview is running. Press Z');
592 | };
593 |
594 | ////////////////////////////////////////////////////////////////////////
595 | //
596 | // Destroy
597 | //
598 | ////////////////////////////////////////////////////////////////////////
599 |
600 | birdview.destroy = function(){
601 |
602 | if(!settings) return;
603 |
604 | if(settings.transition_speed != 0) child.removeEventListener("transitionend", eventHandler, false);
605 |
606 | restoreDOM();
607 |
608 | reference_zoom_level = null;
609 |
610 | if('ontouchstart' in window){
611 | document.removeEventListener('touchstart', onTouchStart, {passive: false});
612 | document.removeEventListener('touchmove', onTouchMove, {passive: false});
613 | }else{
614 | }
615 |
616 | document.removeEventListener('keydown', eventHandler, false);
617 |
618 | document.removeEventListener('click', eventHandler, false);
619 |
620 | window.removeEventListener('scroll', eventHandler, false);
621 |
622 | window.removeEventListener('resize', eventHandler, false);
623 |
624 | window.removeEventListener("orientationchange", eventHandler, false);
625 |
626 | scaled = false;
627 | settings = null;
628 |
629 | console.log('Birdview was destroyed');
630 | }
631 |
632 | return birdview;
633 | }));
634 |
--------------------------------------------------------------------------------
/src/inject/inject.js:
--------------------------------------------------------------------------------
1 | var hasLoaded = false;
2 | var event;
3 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
4 | if (request.action == 'icon-clicked') {
5 | if (hasLoaded) {
6 | event = new Event('birdview:toggle');
7 | document.dispatchEvent(event);
8 | } else {
9 | hasLoaded = true;
10 |
11 | var link = document.createElement('link');
12 | link.rel = 'stylesheet';
13 | link.type = 'text/css';
14 | link.href = chrome.extension.getURL('src/inject/birdview.css');
15 | document.head.appendChild(link);
16 |
17 | var script = document.createElement('script');
18 | script.src = chrome.extension.getURL('src/inject/birdview.js');
19 | script.onload = function() {
20 | chrome.storage.sync.get(
21 | null, // ie; get everything
22 | function(items) {
23 | event = new CustomEvent('birdview:init', { detail: { options: items }});
24 | document.dispatchEvent(event);
25 | event = new Event('birdview:toggle');
26 | document.dispatchEvent(event);
27 | }
28 | );
29 | }
30 | document.head.appendChild(script);
31 | }
32 | }
33 | });
34 |
--------------------------------------------------------------------------------
/src/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Birdview Options
5 |
15 |
16 |
17 |
18 |
19 |
20 | Save
21 | Reset to Defaults
22 |
23 | Documentation
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/src/options.js:
--------------------------------------------------------------------------------
1 | var options = [
2 | {
3 | display: 'Shortcut (keycode)',
4 | id: 'shortcut',
5 | default: 90,
6 | type: 'number'
7 | },
8 | {
9 | display: 'Show overlay',
10 | id: 'create_overlay',
11 | default: true,
12 | type: 'checkbox'
13 | },
14 | {
15 | display: 'Transition time (seconds)',
16 | id: 'transition_speed',
17 | default: 0.3,
18 | type: 'number'
19 | },
20 | {
21 | display: 'Transition Easing (see http://easings.net)',
22 | id: 'transition_easing',
23 | default: 'ease',
24 | type: 'text'
25 | },
26 | {
27 | display: 'Horizontal position (%)',
28 | id: 'css_transform_origin_X',
29 | default: 50,
30 | type: 'number'
31 | }
32 | ];
33 |
34 | // Saves options to chrome.storage.sync.
35 | function saveOptions(valuesHash) {
36 | chrome.storage.sync.set(
37 | valuesHash,
38 | function() {
39 | // Update status to let user know options were saved.
40 | var status = document.querySelector('#status');
41 | status.innerHTML = 'Options saved';
42 | setTimeout(function() {
43 | status.innerHTML = ' ';
44 | }, 2000);
45 | }
46 | );
47 | }
48 |
49 | function getValueType(type) {
50 | if (type === 'checkbox') {
51 | return 'checked';
52 | } else {
53 | return 'value';
54 | }
55 | }
56 |
57 | function getValueAttr(type, value) {
58 | if (type === 'checkbox' && !value) {
59 | return '';
60 | }
61 | return getValueType(type) + '="' + value + '"';
62 | }
63 |
64 | function createNodeFromString(str) {
65 | var div = document.createElement('div');
66 | div.innerHTML = str;
67 | return div.firstChild;
68 | }
69 |
70 | function loadOptions() {
71 | chrome.storage.sync.get(
72 | // Generate hash of default values
73 | options.reduce(function(memo, option) {
74 | memo[option.id] = option.default;
75 | return memo;
76 | }, {}),
77 | function(items) {
78 | var containerEl = document.querySelector('#container');
79 |
80 | options.forEach(function createOption(option) {
81 | var html;
82 | var valueAttr = getValueAttr(option.type, items[option.id]);
83 | var element = containerEl.querySelector('#' + option.id);
84 | if (element) {
85 | element[getValueType(option.type)] = items[option.id];
86 | } else {
87 | html = ' ' + option.display + ' ';
88 |
89 | containerEl.appendChild(createNodeFromString(html));
90 | }
91 | });
92 | }
93 | );
94 | }
95 |
96 | document.getElementById('save').addEventListener('click', function() {
97 | var valuesHash = options.reduce(function(memo, option) {
98 | memo[option.id] = document.querySelector('#' + option.id)[getValueType(option.type)];
99 | return memo;
100 | }, {});
101 | saveOptions(valuesHash);
102 | });
103 |
104 | document.getElementById('defaults').addEventListener('click', function() {
105 | var valuesHash = options.reduce(function(memo, option) {
106 | memo[option.id] = option.default;
107 | return memo;
108 | }, {});
109 | saveOptions(valuesHash);
110 | loadOptions();
111 | });
112 |
113 | loadOptions();
114 |
--------------------------------------------------------------------------------