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) {
89 if (theme == "sepia") {
91 toolbarColor = "#BF9A73";
92 } else if (theme == "dark") {
94 toolbarColor = "#1A1A1A";
97 toolbarColor = "#F5F5F5";
99 // Relies on the classname order of the body being Theme class, then Font
101 var fontFamilyClass = document.body.className.split(" ")[1];
102 document.body.className = cssClass + " " + fontFamilyClass;
104 document.getElementById('theme-color').content = toolbarColor;
107 var updateLoadingIndicator = function() {
108 var colors = ["red", "yellow", "green", "blue"];
109 return function(isLastPage) {
110 if (!isLastPage && typeof this.colorShuffle == "undefined") {
111 var loader = document.getElementById("loader");
114 this.colorShuffle = setInterval(function() {
115 colorIndex = (colorIndex + 1) % colors.length;
116 loader.className = colors[colorIndex];
119 } else if (isLastPage && typeof this.colorShuffle != "undefined") {
120 clearInterval(this.colorShuffle);
126 * Show the distiller feedback form.
127 * @param questionText The i18n text for the feedback question.
128 * @param yesText The i18n text for the feedback answer 'YES'.
129 * @param noText The i18n text for the feedback answer 'NO'.
131 function showFeedbackForm(questionText, yesText, noText) {
132 // If the distiller is running on iOS, do not show the feedback form. This
133 // variable is set in distiller_viewer.cc before this function is run.
134 if (distiller_on_ios) return;
136 document.getElementById('feedbackYes').innerText = yesText;
137 document.getElementById('feedbackNo').innerText = noText;
138 document.getElementById('feedbackQuestion').innerText = questionText;
140 document.getElementById('contentWrap').style.paddingBottom = '120px';
141 document.getElementById('feedbackContainer').style.display = 'block';
142 var mediaQuery = window.matchMedia("print");
143 mediaQuery.addListener(function (query) {
145 document.getElementById('contentWrap').style.paddingBottom = '0px';
146 document.getElementById('feedbackContainer').style.display = 'none';
148 document.getElementById('contentWrap').style.paddingBottom = '120px';
149 document.getElementById('feedbackContainer').style.display = 'block';
155 * Send feedback about this distilled article.
156 * @param good True if the feedback was positive, false if negative.
158 function sendFeedback(good) {
159 var img = document.createElement('img');
161 img.src = '/feedbackgood';
163 img.src = '/feedbackbad';
165 img.style.display = "none";
166 document.body.appendChild(img);
169 // Add a listener to the "View Original" link to report opt-outs.
170 document.getElementById('closeReaderView').addEventListener('click',
172 var img = document.createElement('img');
173 img.src = "/vieworiginal";
174 img.style.display = "none";
175 document.body.appendChild(img);
178 document.getElementById('feedbackYes').addEventListener('click', function(e) {
180 document.getElementById('feedbackContainer').className += " fadeOut";
183 document.getElementById('feedbackNo').addEventListener('click', function(e) {
185 document.getElementById('feedbackContainer').className += " fadeOut";
188 document.getElementById('feedbackContainer').addEventListener('animationend',
190 document.getElementById('feedbackContainer').style.display = 'none';
191 // Close the gap where the feedback form was.
192 var contentWrap = document.getElementById('contentWrap');
193 contentWrap.style.transition = '0.5s';
194 contentWrap.style.paddingBottom = '0px';
197 document.getElementById('contentWrap').addEventListener('transitionend',
199 var contentWrap = document.getElementById('contentWrap');
200 contentWrap.style.transition = '';
203 var pincher = (function() {
205 // When users pinch in Reader Mode, the page would zoom in or out as if it
206 // is a normal web page allowing user-zoom. At the end of pinch gesture, the
207 // page would do text reflow. These pinch-to-zoom and text reflow effects
208 // are not native, but are emulated using CSS and JavaScript.
210 // In order to achieve near-native zooming and panning frame rate, fake 3D
211 // transform is used so that the layer doesn't repaint for each frame.
213 // After the text reflow, the web content shown in the viewport should
214 // roughly be the same paragraph before zooming.
216 // The control point of font size is the html element, so that both "em" and
217 // "rem" are adjusted.
219 // TODO(wychen): Improve scroll position when elementFromPoint is body.
221 var pinching = false;
222 var fontSizeAnchor = 1.0;
224 var focusElement = null;
228 var clampedScale = 1;
237 // The zooming speed relative to pinching speed.
238 var FONT_SCALE_MULTIPLIER = 0.5;
239 var MIN_SPAN_LENGTH = 20;
241 // The font size is guaranteed to be in px.
243 parseFloat(getComputedStyle(document.documentElement).fontSize);
245 var refreshTransform = function() {
246 var slowedScale = Math.exp(Math.log(scale) * FONT_SCALE_MULTIPLIER);
247 clampedScale = Math.max(0.4, Math.min(2.5, fontSizeAnchor * slowedScale));
249 // Use "fake" 3D transform so that the layer is not repainted.
250 // With 2D transform, the frame rate would be much lower.
251 document.body.style.transform =
252 'translate3d(' + shiftX + 'px,' +
253 shiftY + 'px, 0px)' +
254 'scale(' + clampedScale/fontSizeAnchor + ')';
257 function endPinch() {
260 document.body.style.transformOrigin = '';
261 document.body.style.transform = '';
262 document.documentElement.style.fontSize = clampedScale * baseSize + "px";
264 var rect = focusElement.getBoundingClientRect();
265 var targetTop = focusPos * (rect.bottom - rect.top) + rect.top +
266 document.body.scrollTop - (initClientMid.y + shiftY);
267 document.body.scrollTop = targetTop;
270 function touchSpan(e) {
271 var count = e.touches.length;
272 var mid = touchClientMid(e);
274 for (var i = 0; i < count; i++) {
275 var dx = (e.touches[i].clientX - mid.x);
276 var dy = (e.touches[i].clientY - mid.y);
277 sum += Math.hypot(dx, dy);
279 // Avoid very small span.
280 return Math.max(MIN_SPAN_LENGTH, sum/count);
283 function touchClientMid(e) {
284 var count = e.touches.length;
287 for (var i = 0; i < count; i++) {
288 sumX += e.touches[i].clientX;
289 sumY += e.touches[i].clientY;
291 return {x: sumX/count, y: sumY/count};
294 function touchPageMid(e) {
295 var clientMid = touchClientMid(e);
296 return {x: clientMid.x - e.touches[0].clientX + e.touches[0].pageX,
297 y: clientMid.y - e.touches[0].clientY + e.touches[0].pageY};
301 handleTouchStart: function(e) {
302 if (e.touches.length < 2) return;
305 var span = touchSpan(e);
306 var clientMid = touchClientMid(e);
308 if (e.touches.length > 2) {
310 lastClientMid = clientMid;
321 parseFloat(getComputedStyle(document.documentElement).fontSize) /
324 var pinchOrigin = touchPageMid(e);
325 document.body.style.transformOrigin =
326 pinchOrigin.x + 'px ' + pinchOrigin.y + 'px';
328 // Try to preserve the pinching center after text reflow.
329 // This is accurate to the HTML element level.
330 focusElement = document.elementFromPoint(clientMid.x, clientMid.y);
331 var rect = focusElement.getBoundingClientRect();
332 initClientMid = clientMid;
333 focusPos = (initClientMid.y - rect.top) / (rect.bottom - rect.top);
336 lastClientMid = clientMid;
341 handleTouchMove: function(e) {
342 if (!pinching) return;
343 if (e.touches.length < 2) return;
346 var span = touchSpan(e);
347 var clientMid = touchClientMid(e);
349 scale *= touchSpan(e) / lastSpan;
350 shiftX += clientMid.x - lastClientMid.x;
351 shiftY += clientMid.y - lastClientMid.y;
356 lastClientMid = clientMid;
359 handleTouchEnd: function(e) {
360 if (!pinching) return;
363 var span = touchSpan(e);
364 var clientMid = touchClientMid(e);
366 if (e.touches.length >= 2) {
368 lastClientMid = clientMid;
376 handleTouchCancel: function(e) {
385 document.documentElement.style.fontSize = clampedScale * baseSize + "px";
391 clampedScale: clampedScale,
399 window.addEventListener('touchstart', pincher.handleTouchStart, false);
400 window.addEventListener('touchmove', pincher.handleTouchMove, false);
401 window.addEventListener('touchend', pincher.handleTouchEnd, false);
402 window.addEventListener('touchcancel', pincher.handleTouchCancel, false);