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();
15 function fillYouTubePlaceholders() {
16 var placeholders
= document
.getElementsByClassName('embed-placeholder');
17 for (var i
= 0; i
< placeholders
.length
; i
++) {
18 if (!placeholders
[i
].hasAttribute('data-type') ||
19 placeholders
[i
].getAttribute('data-type') != 'youtube' ||
20 !placeholders
[i
].hasAttribute('data-id')) {
23 var embed
= document
.createElement('iframe');
24 var url
= 'http://www.youtube.com/embed/' +
25 placeholders
[i
].getAttribute('data-id');
26 embed
.setAttribute('class', 'youtubeIframe');
27 embed
.setAttribute('src', url
);
28 embed
.setAttribute('type', 'text/html');
29 embed
.setAttribute('frameborder', '0');
31 var parent
= placeholders
[i
].parentElement
;
32 var container
= document
.createElement('div');
33 container
.setAttribute('class', 'youtubeContainer');
34 container
.appendChild(embed
);
36 parent
.replaceChild(container
, placeholders
[i
]);
40 function showLoadingIndicator(isLastPage
) {
41 document
.getElementById('loadingIndicator').className
=
42 isLastPage
? 'hidden' : 'visible';
43 updateLoadingIndicator(isLastPage
);
47 function setTitle(title
) {
48 var holder
= document
.getElementById('titleHolder');
50 holder
.textContent
= title
;
51 document
.title
= title
;
54 // Set the text direction of the document ('ltr', 'rtl', or 'auto').
55 function setTextDirection(direction
) {
56 document
.body
.setAttribute('dir', direction
);
59 // Maps JS Font Family to CSS class and then changes body class name.
60 // CSS classes must agree with distilledpage.css.
61 function useFontFamily(fontFamily
) {
63 if (fontFamily
== "serif") {
65 } else if (fontFamily
== "monospace") {
66 cssClass
= "monospace";
68 cssClass
= "sans-serif";
70 // Relies on the classname order of the body being Theme class, then Font
72 var themeClass
= document
.body
.className
.split(" ")[0];
73 document
.body
.className
= themeClass
+ " " + cssClass
;
76 // Maps JS theme to CSS class and then changes body class name.
77 // CSS classes must agree with distilledpage.css.
78 function useTheme(theme
) {
80 if (theme
== "sepia") {
82 } else if (theme
== "dark") {
87 // Relies on the classname order of the body being Theme class, then Font
89 var fontFamilyClass
= document
.body
.className
.split(" ")[1];
90 document
.body
.className
= cssClass
+ " " + fontFamilyClass
;
93 var updateLoadingIndicator = function() {
94 var colors
= ["red", "yellow", "green", "blue"];
95 return function(isLastPage
) {
96 if (!isLastPage
&& typeof this.colorShuffle
== "undefined") {
97 var loader
= document
.getElementById("loader");
100 this.colorShuffle
= setInterval(function() {
101 colorIndex
= (colorIndex
+ 1) % colors
.length
;
102 loader
.className
= colors
[colorIndex
];
105 } else if (isLastPage
&& typeof this.colorShuffle
!= "undefined") {
106 clearInterval(this.colorShuffle
);
112 * Show the distiller feedback form.
113 * @param questionText The i18n text for the feedback question.
114 * @param yesText The i18n text for the feedback answer 'YES'.
115 * @param noText The i18n text for the feedback answer 'NO'.
117 function showFeedbackForm(questionText
, yesText
, noText
) {
118 // If the distiller is running on iOS, do not show the feedback form. This
119 // variable is set in distiller_viewer.cc before this function is run.
120 if (distiller_on_ios
) return;
122 document
.getElementById('feedbackYes').innerText
= yesText
;
123 document
.getElementById('feedbackNo').innerText
= noText
;
124 document
.getElementById('feedbackQuestion').innerText
= questionText
;
126 document
.getElementById('contentWrap').style
.paddingBottom
= '120px';
127 document
.getElementById('feedbackContainer').style
.display
= 'block';
131 * Send feedback about this distilled article.
132 * @param good True if the feedback was positive, false if negative.
134 function sendFeedback(good
) {
135 var img
= document
.createElement('img');
137 img
.src
= '/feedbackgood';
139 img
.src
= '/feedbackbad';
141 img
.style
.display
= "none";
142 document
.body
.appendChild(img
);
145 // Add a listener to the "View Original" link to report opt-outs.
146 document
.getElementById('closeReaderView').addEventListener('click',
148 var img
= document
.createElement('img');
149 img
.src
= "/vieworiginal";
150 img
.style
.display
= "none";
151 document
.body
.appendChild(img
);
154 document
.getElementById('feedbackYes').addEventListener('click', function(e
) {
156 document
.getElementById('feedbackContainer').className
+= " fadeOut";
159 document
.getElementById('feedbackNo').addEventListener('click', function(e
) {
161 document
.getElementById('feedbackContainer').className
+= " fadeOut";
164 document
.getElementById('feedbackContainer').addEventListener('animationend',
166 document
.getElementById('feedbackContainer').style
.display
= 'none';
167 // Close the gap where the feedback form was.
168 var contentWrap
= document
.getElementById('contentWrap');
169 contentWrap
.style
.transition
= '0.5s';
170 contentWrap
.style
.paddingBottom
= '0px';
173 document
.getElementById('contentWrap').addEventListener('transitionend',
175 var contentWrap
= document
.getElementById('contentWrap');
176 contentWrap
.style
.transition
= '';
179 var pincher
= (function() {
181 // When users pinch in Reader Mode, the page would zoom in or out as if it
182 // is a normal web page allowing user-zoom. At the end of pinch gesture, the
183 // page would do text reflow. These pinch-to-zoom and text reflow effects
184 // are not native, but are emulated using CSS and JavaScript.
186 // In order to achieve near-native zooming and panning frame rate, fake 3D
187 // transform is used so that the layer doesn't repaint for each frame.
189 // After the text reflow, the web content shown in the viewport should
190 // roughly be the same paragraph before zooming.
192 // The control point of font size is the html element, so that both "em" and
193 // "rem" are adjusted.
195 // TODO(wychen): Improve scroll position when elementFromPoint is body.
197 var pinching
= false;
198 var fontSizeAnchor
= 1.0;
200 var focusElement
= null;
204 var clampedScale
= 1;
213 // The zooming speed relative to pinching speed.
214 var FONT_SCALE_MULTIPLIER
= 0.5;
215 var MIN_SPAN_LENGTH
= 20;
217 // The font size is guaranteed to be in px.
219 parseFloat(getComputedStyle(document
.documentElement
).fontSize
);
221 var refreshTransform = function() {
222 var slowedScale
= Math
.exp(Math
.log(scale
) * FONT_SCALE_MULTIPLIER
);
223 clampedScale
= Math
.max(0.4, Math
.min(2.5, fontSizeAnchor
* slowedScale
));
225 // Use "fake" 3D transform so that the layer is not repainted.
226 // With 2D transform, the frame rate would be much lower.
227 document
.body
.style
.transform
=
228 'translate3d(' + shiftX
+ 'px,' +
229 shiftY
+ 'px, 0px)' +
230 'scale(' + clampedScale
/fontSizeAnchor
+ ')';
233 function endPinch() {
236 document
.body
.style
.transformOrigin
= '';
237 document
.body
.style
.transform
= '';
238 document
.documentElement
.style
.fontSize
= clampedScale
* baseSize
+ "px";
240 var rect
= focusElement
.getBoundingClientRect();
241 var targetTop
= focusPos
* (rect
.bottom
- rect
.top
) + rect
.top
+
242 document
.body
.scrollTop
- (initClientMid
.y
+ shiftY
);
243 document
.body
.scrollTop
= targetTop
;
246 function touchSpan(e
) {
247 var count
= e
.touches
.length
;
248 var mid
= touchClientMid(e
);
250 for (var i
= 0; i
< count
; i
++) {
251 var dx
= (e
.touches
[i
].clientX
- mid
.x
);
252 var dy
= (e
.touches
[i
].clientY
- mid
.y
);
253 sum
+= Math
.hypot(dx
, dy
);
255 // Avoid very small span.
256 return Math
.max(MIN_SPAN_LENGTH
, sum
/count
);
259 function touchClientMid(e
) {
260 var count
= e
.touches
.length
;
263 for (var i
= 0; i
< count
; i
++) {
264 sumX
+= e
.touches
[i
].clientX
;
265 sumY
+= e
.touches
[i
].clientY
;
267 return {x
: sumX
/count, y: sumY/count};
270 function touchPageMid(e
) {
271 var clientMid
= touchClientMid(e
);
272 return {x
: clientMid
.x
- e
.touches
[0].clientX
+ e
.touches
[0].pageX
,
273 y
: clientMid
.y
- e
.touches
[0].clientY
+ e
.touches
[0].pageY
};
277 handleTouchStart: function(e
) {
278 if (e
.touches
.length
< 2) return;
281 var span
= touchSpan(e
);
282 var clientMid
= touchClientMid(e
);
284 if (e
.touches
.length
> 2) {
286 lastClientMid
= clientMid
;
297 parseFloat(getComputedStyle(document
.documentElement
).fontSize
) /
300 var pinchOrigin
= touchPageMid(e
);
301 document
.body
.style
.transformOrigin
=
302 pinchOrigin
.x
+ 'px ' + pinchOrigin
.y
+ 'px';
304 // Try to preserve the pinching center after text reflow.
305 // This is accurate to the HTML element level.
306 focusElement
= document
.elementFromPoint(clientMid
.x
, clientMid
.y
);
307 var rect
= focusElement
.getBoundingClientRect();
308 initClientMid
= clientMid
;
309 focusPos
= (initClientMid
.y
- rect
.top
) / (rect
.bottom
- rect
.top
);
312 lastClientMid
= clientMid
;
317 handleTouchMove: function(e
) {
318 if (!pinching
) return;
319 if (e
.touches
.length
< 2) return;
322 var span
= touchSpan(e
);
323 var clientMid
= touchClientMid(e
);
325 scale
*= touchSpan(e
) / lastSpan
;
326 shiftX
+= clientMid
.x
- lastClientMid
.x
;
327 shiftY
+= clientMid
.y
- lastClientMid
.y
;
332 lastClientMid
= clientMid
;
335 handleTouchEnd: function(e
) {
336 if (!pinching
) return;
339 var span
= touchSpan(e
);
340 var clientMid
= touchClientMid(e
);
342 if (e
.touches
.length
>= 2) {
344 lastClientMid
= clientMid
;
352 handleTouchCancel: function(e
) {
361 document
.documentElement
.style
.fontSize
= clampedScale
* baseSize
+ "px";
367 clampedScale
: clampedScale
,
375 window
.addEventListener('touchstart', pincher
.handleTouchStart
, false);
376 window
.addEventListener('touchmove', pincher
.handleTouchMove
, false);
377 window
.addEventListener('touchend', pincher
.handleTouchEnd
, false);
378 window
.addEventListener('touchcancel', pincher
.handleTouchCancel
, false);