1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // This variable will be changed by iOS scripts.
6 var distiller_on_ios
= false;
8 function addToPage(html
) {
9 var div
= document
.createElement('div');
11 document
.getElementById('content').appendChild(div
);
12 fillYouTubePlaceholders();
14 if (typeof navigate_on_initial_content_load
!== 'undefined' &&
15 navigate_on_initial_content_load
) {
16 navigate_on_initial_content_load
= false;
17 setTimeout(function() {
18 window
.location
= window
.location
+ "#loaded";
23 function fillYouTubePlaceholders() {
24 var placeholders
= document
.getElementsByClassName('embed-placeholder');
25 for (var i
= 0; i
< placeholders
.length
; i
++) {
26 if (!placeholders
[i
].hasAttribute('data-type') ||
27 placeholders
[i
].getAttribute('data-type') != 'youtube' ||
28 !placeholders
[i
].hasAttribute('data-id')) {
31 var embed
= document
.createElement('iframe');
32 var url
= 'http://www.youtube.com/embed/' +
33 placeholders
[i
].getAttribute('data-id');
34 embed
.setAttribute('class', 'youtubeIframe');
35 embed
.setAttribute('src', url
);
36 embed
.setAttribute('type', 'text/html');
37 embed
.setAttribute('frameborder', '0');
39 var parent
= placeholders
[i
].parentElement
;
40 var container
= document
.createElement('div');
41 container
.setAttribute('class', 'youtubeContainer');
42 container
.appendChild(embed
);
44 parent
.replaceChild(container
, placeholders
[i
]);
48 function showLoadingIndicator(isLastPage
) {
49 document
.getElementById('loadingIndicator').className
=
50 isLastPage
? 'hidden' : 'visible';
51 updateLoadingIndicator(isLastPage
);
55 function setTitle(title
) {
56 var holder
= document
.getElementById('titleHolder');
58 holder
.textContent
= title
;
59 document
.title
= title
;
62 // Set the text direction of the document ('ltr', 'rtl', or 'auto').
63 function setTextDirection(direction
) {
64 document
.body
.setAttribute('dir', direction
);
67 // Maps JS Font Family to CSS class and then changes body class name.
68 // CSS classes must agree with distilledpage.css.
69 function useFontFamily(fontFamily
) {
71 if (fontFamily
== "serif") {
73 } else if (fontFamily
== "monospace") {
74 cssClass
= "monospace";
76 cssClass
= "sans-serif";
78 // Relies on the classname order of the body being Theme class, then Font
80 var themeClass
= document
.body
.className
.split(" ")[0];
81 document
.body
.className
= themeClass
+ " " + cssClass
;
84 // Maps JS theme to CSS class and then changes body class name.
85 // CSS classes must agree with distilledpage.css.
86 function useTheme(theme
) {
88 if (theme
== "sepia") {
90 } else if (theme
== "dark") {
95 // Relies on the classname order of the body being Theme class, then Font
97 var fontFamilyClass
= document
.body
.className
.split(" ")[1];
98 document
.body
.className
= cssClass
+ " " + fontFamilyClass
;
100 updateToolbarColor();
103 function updateToolbarColor() {
104 // Relies on the classname order of the body being Theme class, then Font
106 var themeClass
= document
.body
.className
.split(" ")[0];
109 if (themeClass
== "sepia") {
110 toolbarColor
= "#BF9A73";
111 } else if (themeClass
== "dark") {
112 toolbarColor
= "#1A1A1A";
114 toolbarColor
= "#F5F5F5";
116 document
.getElementById('theme-color').content
= toolbarColor
;
119 var updateLoadingIndicator = function() {
120 var colors
= ["red", "yellow", "green", "blue"];
121 return function(isLastPage
) {
122 if (!isLastPage
&& typeof this.colorShuffle
== "undefined") {
123 var loader
= document
.getElementById("loader");
126 this.colorShuffle
= setInterval(function() {
127 colorIndex
= (colorIndex
+ 1) % colors
.length
;
128 loader
.className
= colors
[colorIndex
];
131 } else if (isLastPage
&& typeof this.colorShuffle
!= "undefined") {
132 clearInterval(this.colorShuffle
);
138 * Show the distiller feedback form.
139 * @param questionText The i18n text for the feedback question.
140 * @param yesText The i18n text for the feedback answer 'YES'.
141 * @param noText The i18n text for the feedback answer 'NO'.
143 function showFeedbackForm(questionText
, yesText
, noText
) {
144 // If the distiller is running on iOS, do not show the feedback form. This
145 // variable is set in distiller_viewer.cc before this function is run.
146 if (distiller_on_ios
) return;
148 document
.getElementById('feedbackYes').innerText
= yesText
;
149 document
.getElementById('feedbackNo').innerText
= noText
;
150 document
.getElementById('feedbackQuestion').innerText
= questionText
;
152 document
.getElementById('feedbackContainer').classList
.remove("hidden");
156 * Send feedback about this distilled article.
157 * @param good True if the feedback was positive, false if negative.
159 function sendFeedback(good
) {
160 var img
= document
.createElement('img');
162 img
.src
= '/feedbackgood';
164 img
.src
= '/feedbackbad';
166 img
.style
.display
= "none";
167 document
.body
.appendChild(img
);
170 // Add a listener to the "View Original" link to report opt-outs.
171 document
.getElementById('closeReaderView').addEventListener('click',
173 var img
= document
.createElement('img');
174 img
.src
= "/vieworiginal";
175 img
.style
.display
= "none";
176 document
.body
.appendChild(img
);
179 document
.getElementById('feedbackYes').addEventListener('click', function(e
) {
181 document
.getElementById('feedbackContainer').className
+= " fadeOut";
184 document
.getElementById('feedbackNo').addEventListener('click', function(e
) {
186 document
.getElementById('feedbackContainer').className
+= " fadeOut";
189 document
.getElementById('feedbackContainer').addEventListener('animationend',
191 var feedbackContainer
= document
.getElementById('feedbackContainer');
192 feedbackContainer
.classList
.remove("fadeOut");
193 feedbackContainer
.className
+= " hidden";
194 document
.getElementById('contentWrap').style
.paddingBottom
= '120px';
195 setTimeout(function() {
196 // Close the gap where the feedback form was.
197 var contentWrap
= document
.getElementById('contentWrap');
198 contentWrap
.style
.transition
= '0.5s';
199 contentWrap
.style
.paddingBottom
= '';
203 document
.getElementById('contentWrap').addEventListener('transitionend',
205 var contentWrap
= document
.getElementById('contentWrap');
206 contentWrap
.style
.transition
= '';
209 updateToolbarColor();
211 var pincher
= (function() {
213 // When users pinch in Reader Mode, the page would zoom in or out as if it
214 // is a normal web page allowing user-zoom. At the end of pinch gesture, the
215 // page would do text reflow. These pinch-to-zoom and text reflow effects
216 // are not native, but are emulated using CSS and JavaScript.
218 // In order to achieve near-native zooming and panning frame rate, fake 3D
219 // transform is used so that the layer doesn't repaint for each frame.
221 // After the text reflow, the web content shown in the viewport should
222 // roughly be the same paragraph before zooming.
224 // The control point of font size is the html element, so that both "em" and
225 // "rem" are adjusted.
227 // TODO(wychen): Improve scroll position when elementFromPoint is body.
229 var pinching
= false;
230 var fontSizeAnchor
= 1.0;
232 var focusElement
= null;
236 var clampedScale
= 1;
245 // The zooming speed relative to pinching speed.
246 var FONT_SCALE_MULTIPLIER
= 0.5;
247 var MIN_SPAN_LENGTH
= 20;
249 // The font size is guaranteed to be in px.
251 parseFloat(getComputedStyle(document
.documentElement
).fontSize
);
253 var refreshTransform = function() {
254 var slowedScale
= Math
.exp(Math
.log(scale
) * FONT_SCALE_MULTIPLIER
);
255 clampedScale
= Math
.max(0.4, Math
.min(2.5, fontSizeAnchor
* slowedScale
));
257 // Use "fake" 3D transform so that the layer is not repainted.
258 // With 2D transform, the frame rate would be much lower.
259 document
.body
.style
.transform
=
260 'translate3d(' + shiftX
+ 'px,' +
261 shiftY
+ 'px, 0px)' +
262 'scale(' + clampedScale
/fontSizeAnchor
+ ')';
265 function endPinch() {
268 document
.body
.style
.transformOrigin
= '';
269 document
.body
.style
.transform
= '';
270 document
.documentElement
.style
.fontSize
= clampedScale
* baseSize
+ "px";
272 var rect
= focusElement
.getBoundingClientRect();
273 var targetTop
= focusPos
* (rect
.bottom
- rect
.top
) + rect
.top
+
274 document
.body
.scrollTop
- (initClientMid
.y
+ shiftY
);
275 document
.body
.scrollTop
= targetTop
;
278 function touchSpan(e
) {
279 var count
= e
.touches
.length
;
280 var mid
= touchClientMid(e
);
282 for (var i
= 0; i
< count
; i
++) {
283 var dx
= (e
.touches
[i
].clientX
- mid
.x
);
284 var dy
= (e
.touches
[i
].clientY
- mid
.y
);
285 sum
+= Math
.hypot(dx
, dy
);
287 // Avoid very small span.
288 return Math
.max(MIN_SPAN_LENGTH
, sum
/count
);
291 function touchClientMid(e
) {
292 var count
= e
.touches
.length
;
295 for (var i
= 0; i
< count
; i
++) {
296 sumX
+= e
.touches
[i
].clientX
;
297 sumY
+= e
.touches
[i
].clientY
;
299 return {x
: sumX
/count, y: sumY/count};
302 function touchPageMid(e
) {
303 var clientMid
= touchClientMid(e
);
304 return {x
: clientMid
.x
- e
.touches
[0].clientX
+ e
.touches
[0].pageX
,
305 y
: clientMid
.y
- e
.touches
[0].clientY
+ e
.touches
[0].pageY
};
309 handleTouchStart: function(e
) {
310 if (e
.touches
.length
< 2) return;
313 var span
= touchSpan(e
);
314 var clientMid
= touchClientMid(e
);
316 if (e
.touches
.length
> 2) {
318 lastClientMid
= clientMid
;
329 parseFloat(getComputedStyle(document
.documentElement
).fontSize
) /
332 var pinchOrigin
= touchPageMid(e
);
333 document
.body
.style
.transformOrigin
=
334 pinchOrigin
.x
+ 'px ' + pinchOrigin
.y
+ 'px';
336 // Try to preserve the pinching center after text reflow.
337 // This is accurate to the HTML element level.
338 focusElement
= document
.elementFromPoint(clientMid
.x
, clientMid
.y
);
339 var rect
= focusElement
.getBoundingClientRect();
340 initClientMid
= clientMid
;
341 focusPos
= (initClientMid
.y
- rect
.top
) / (rect
.bottom
- rect
.top
);
344 lastClientMid
= clientMid
;
349 handleTouchMove: function(e
) {
350 if (!pinching
) return;
351 if (e
.touches
.length
< 2) return;
354 var span
= touchSpan(e
);
355 var clientMid
= touchClientMid(e
);
357 scale
*= touchSpan(e
) / lastSpan
;
358 shiftX
+= clientMid
.x
- lastClientMid
.x
;
359 shiftY
+= clientMid
.y
- lastClientMid
.y
;
364 lastClientMid
= clientMid
;
367 handleTouchEnd: function(e
) {
368 if (!pinching
) return;
371 var span
= touchSpan(e
);
372 var clientMid
= touchClientMid(e
);
374 if (e
.touches
.length
>= 2) {
376 lastClientMid
= clientMid
;
384 handleTouchCancel: function(e
) {
393 document
.documentElement
.style
.fontSize
= clampedScale
* baseSize
+ "px";
399 clampedScale
: clampedScale
,
407 window
.addEventListener('touchstart', pincher
.handleTouchStart
, false);
408 window
.addEventListener('touchmove', pincher
.handleTouchMove
, false);
409 window
.addEventListener('touchend', pincher
.handleTouchEnd
, false);
410 window
.addEventListener('touchcancel', pincher
.handleTouchCancel
, false);