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 function addToPage(html
) {
6 var div
= document
.createElement('div');
8 document
.getElementById('content').appendChild(div
);
9 fillYouTubePlaceholders();
12 function fillYouTubePlaceholders() {
13 var placeholders
= document
.getElementsByClassName('embed-placeholder');
14 for (var i
= 0; i
< placeholders
.length
; i
++) {
15 if (!placeholders
[i
].hasAttribute('data-type') ||
16 placeholders
[i
].getAttribute('data-type') != 'youtube' ||
17 !placeholders
[i
].hasAttribute('data-id')) {
20 var embed
= document
.createElement('iframe');
21 var url
= 'http://www.youtube.com/embed/' +
22 placeholders
[i
].getAttribute('data-id');
23 embed
.setAttribute('class', 'youtubeIframe');
24 embed
.setAttribute('src', url
);
25 embed
.setAttribute('type', 'text/html');
26 embed
.setAttribute('frameborder', '0');
28 var parent
= placeholders
[i
].parentElement
;
29 var container
= document
.createElement('div');
30 container
.setAttribute('class', 'youtubeContainer');
31 container
.appendChild(embed
);
33 parent
.replaceChild(container
, placeholders
[i
]);
37 function showLoadingIndicator(isLastPage
) {
38 document
.getElementById('loadingIndicator').className
=
39 isLastPage
? 'hidden' : 'visible';
40 updateLoadingIndicator(isLastPage
);
43 // Sets the title. The title will be exposed with a simple animation. This
44 // should only be used when the title was not included in the initial html.
45 function setTitle(title
) {
46 var holder
= document
.getElementById('titleHolder');
47 var collapse
= document
.getElementById('titleCollapse');
49 collapse
.style
.height
= "0px";
51 holder
.textContent
= title
;
52 var newHeight
= Math
.max(90, holder
.getBoundingClientRect().height
);
54 collapse
.style
.transition
= "height 0.2s";
55 collapse
.style
.height
= newHeight
+ "px";
58 // Maps JS Font Family to CSS class and then changes body class name.
59 // CSS classes must agree with distilledpage.css.
60 function useFontFamily(fontFamily
) {
62 if (fontFamily
== "serif") {
64 } else if (fontFamily
== "monospace") {
65 cssClass
= "monospace";
67 cssClass
= "sans-serif";
69 // Relies on the classname order of the body being Theme class, then Font
71 var themeClass
= document
.body
.className
.split(" ")[0];
72 document
.body
.className
= themeClass
+ " " + cssClass
;
75 // Maps JS theme to CSS class and then changes body class name.
76 // CSS classes must agree with distilledpage.css.
77 function useTheme(theme
) {
79 if (theme
== "sepia") {
81 } else if (theme
== "dark") {
86 // Relies on the classname order of the body being Theme class, then Font
88 var fontFamilyClass
= document
.body
.className
.split(" ")[1];
89 document
.body
.className
= cssClass
+ " " + fontFamilyClass
;
92 var updateLoadingIndicator = function() {
93 var colors
= ["red", "yellow", "green", "blue"];
94 return function(isLastPage
) {
95 if (!isLastPage
&& typeof this.colorShuffle
== "undefined") {
96 var loader
= document
.getElementById("loader");
99 this.colorShuffle
= setInterval(function() {
100 colorIndex
= (colorIndex
+ 1) % colors
.length
;
101 loader
.className
= colors
[colorIndex
];
104 } else if (isLastPage
&& typeof this.colorShuffle
!= "undefined") {
105 clearInterval(this.colorShuffle
);
111 * Show the distiller feedback form.
112 * @param questionText The i18n text for the feedback question.
113 * @param yesText The i18n text for the feedback answer 'YES'.
114 * @param noText The i18n text for the feedback answer 'NO'.
116 function showFeedbackForm(questionText
, yesText
, noText
) {
117 document
.getElementById('feedbackYes').innerText
= yesText
;
118 document
.getElementById('feedbackNo').innerText
= noText
;
119 document
.getElementById('feedbackQuestion').innerText
= questionText
;
121 document
.getElementById('feedbackContainer').style
.display
= 'block';
125 * Send feedback about this distilled article.
126 * @param good True if the feedback was positive, false if negative.
128 function sendFeedback(good
) {
129 var img
= document
.createElement('img');
131 img
.src
= '/feedbackgood';
133 img
.src
= '/feedbackbad';
135 img
.style
.display
= "none";
136 document
.body
.appendChild(img
);
139 // Add a listener to the "View Original" link to report opt-outs.
140 document
.getElementById('showOriginal').addEventListener('click', function(e
) {
141 var img
= document
.createElement('img');
142 img
.src
= "/vieworiginal";
143 img
.style
.display
= "none";
144 document
.body
.appendChild(img
);
147 document
.getElementById('feedbackYes').addEventListener('click', function(e
) {
149 document
.getElementById('feedbackContainer').className
+= " fadeOut";
152 document
.getElementById('feedbackNo').addEventListener('click', function(e
) {
154 document
.getElementById('feedbackContainer').className
+= " fadeOut";
157 document
.getElementById('feedbackContainer').addEventListener('animationend',
159 document
.getElementById('feedbackContainer').style
.display
= 'none';
160 // Close the gap where the feedback form was.
161 var contentWrap
= document
.getElementById('contentWrap');
162 contentWrap
.style
.transition
= '0.5s';
163 contentWrap
.style
.paddingBottom
= '0px';
166 document
.getElementById('contentWrap').addEventListener('transitionend',
168 var contentWrap
= document
.getElementById('contentWrap');
169 contentWrap
.style
.transition
= '';
172 var pincher
= (function() {
174 // When users pinch in Reader Mode, the page would zoom in or out as if it
175 // is a normal web page allowing user-zoom. At the end of pinch gesture, the
176 // page would do text reflow. These pinch-to-zoom and text reflow effects
177 // are not native, but are emulated using CSS and JavaScript.
179 // In order to achieve near-native zooming and panning frame rate, fake 3D
180 // transform is used so that the layer doesn't repaint for each frame.
182 // After the text reflow, the web content shown in the viewport should
183 // roughly be the same paragraph before zooming.
185 // The control point of font size is the html element, so that both "em" and
186 // "rem" are adjusted.
188 // TODO(wychen): Improve scroll position when elementFromPoint is body.
190 var pinching
= false;
191 var fontSizeAnchor
= 1.0;
193 var focusElement
= null;
197 var clampedScale
= 1;
206 // The zooming speed relative to pinching speed.
207 var FONT_SCALE_MULTIPLIER
= 0.5;
208 var MIN_SPAN_LENGTH
= 20;
210 // The font size is guaranteed to be in px.
212 parseFloat(getComputedStyle(document
.documentElement
).fontSize
);
214 var refreshTransform = function() {
215 var slowedScale
= Math
.exp(Math
.log(scale
) * FONT_SCALE_MULTIPLIER
);
216 clampedScale
= Math
.max(0.4, Math
.min(2.5, fontSizeAnchor
* slowedScale
));
218 // Use "fake" 3D transform so that the layer is not repainted.
219 // With 2D transform, the frame rate would be much lower.
220 document
.body
.style
.transform
=
221 'translate3d(' + shiftX
+ 'px,' +
222 shiftY
+ 'px, 0px)' +
223 'scale(' + clampedScale
/fontSizeAnchor
+ ')';
226 function endPinch() {
229 document
.body
.style
.transformOrigin
= '';
230 document
.body
.style
.transform
= '';
231 document
.documentElement
.style
.fontSize
= clampedScale
* baseSize
+ "px";
233 var rect
= focusElement
.getBoundingClientRect();
234 var targetTop
= focusPos
* (rect
.bottom
- rect
.top
) + rect
.top
+
235 document
.body
.scrollTop
- (initClientMid
.y
+ shiftY
);
236 document
.body
.scrollTop
= targetTop
;
239 function touchSpan(e
) {
240 var count
= e
.touches
.length
;
241 var mid
= touchClientMid(e
);
243 for (var i
= 0; i
< count
; i
++) {
244 var dx
= (e
.touches
[i
].clientX
- mid
.x
);
245 var dy
= (e
.touches
[i
].clientY
- mid
.y
);
246 sum
+= Math
.hypot(dx
, dy
);
248 // Avoid very small span.
249 return Math
.max(MIN_SPAN_LENGTH
, sum
/count
);
252 function touchClientMid(e
) {
253 var count
= e
.touches
.length
;
256 for (var i
= 0; i
< count
; i
++) {
257 sumX
+= e
.touches
[i
].clientX
;
258 sumY
+= e
.touches
[i
].clientY
;
260 return {x
: sumX
/count, y: sumY/count};
263 function touchPageMid(e
) {
264 var clientMid
= touchClientMid(e
);
265 return {x
: clientMid
.x
- e
.touches
[0].clientX
+ e
.touches
[0].pageX
,
266 y
: clientMid
.y
- e
.touches
[0].clientY
+ e
.touches
[0].pageY
};
270 handleTouchStart: function(e
) {
271 if (e
.touches
.length
< 2) return;
274 var span
= touchSpan(e
);
275 var clientMid
= touchClientMid(e
);
277 if (e
.touches
.length
> 2) {
279 lastClientMid
= clientMid
;
290 parseFloat(getComputedStyle(document
.documentElement
).fontSize
) /
293 var pinchOrigin
= touchPageMid(e
);
294 document
.body
.style
.transformOrigin
=
295 pinchOrigin
.x
+ 'px ' + pinchOrigin
.y
+ 'px';
297 // Try to preserve the pinching center after text reflow.
298 // This is accurate to the HTML element level.
299 focusElement
= document
.elementFromPoint(clientMid
.x
, clientMid
.y
);
300 var rect
= focusElement
.getBoundingClientRect();
301 initClientMid
= clientMid
;
302 focusPos
= (initClientMid
.y
- rect
.top
) / (rect
.bottom
- rect
.top
);
305 lastClientMid
= clientMid
;
310 handleTouchMove: function(e
) {
311 if (!pinching
) return;
312 if (e
.touches
.length
< 2) return;
315 var span
= touchSpan(e
);
316 var clientMid
= touchClientMid(e
);
318 scale
*= touchSpan(e
) / lastSpan
;
319 shiftX
+= clientMid
.x
- lastClientMid
.x
;
320 shiftY
+= clientMid
.y
- lastClientMid
.y
;
325 lastClientMid
= clientMid
;
328 handleTouchEnd: function(e
) {
329 if (!pinching
) return;
332 var span
= touchSpan(e
);
333 var clientMid
= touchClientMid(e
);
335 if (e
.touches
.length
>= 2) {
337 lastClientMid
= clientMid
;
345 handleTouchCancel: function(e
) {
354 document
.documentElement
.style
.fontSize
= clampedScale
* baseSize
+ "px";
360 clampedScale
: clampedScale
,
368 window
.addEventListener('touchstart', pincher
.handleTouchStart
, false);
369 window
.addEventListener('touchmove', pincher
.handleTouchMove
, false);
370 window
.addEventListener('touchend', pincher
.handleTouchEnd
, false);
371 window
.addEventListener('touchcancel', pincher
.handleTouchCancel
, false);