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('contentWrap').style.paddingBottom = '120px';
153 document.getElementById('feedbackContainer').style.display = 'block';
154 var mediaQuery = window.matchMedia("print");
155 mediaQuery.addListener(function (query) {
157 document.getElementById('contentWrap').style.paddingBottom = '0px';
158 document.getElementById('feedbackContainer').style.display = 'none';
160 document.getElementById('contentWrap').style.paddingBottom = '120px';
161 document.getElementById('feedbackContainer').style.display = 'block';
167 * Send feedback about this distilled article.
168 * @param good True if the feedback was positive, false if negative.
170 function sendFeedback(good) {
171 var img = document.createElement('img');
173 img.src = '/feedbackgood';
175 img.src = '/feedbackbad';
177 img.style.display = "none";
178 document.body.appendChild(img);
181 // Add a listener to the "View Original" link to report opt-outs.
182 document.getElementById('closeReaderView').addEventListener('click',
184 var img = document.createElement('img');
185 img.src = "/vieworiginal";
186 img.style.display = "none";
187 document.body.appendChild(img);
190 document.getElementById('feedbackYes').addEventListener('click', function(e) {
192 document.getElementById('feedbackContainer').className += " fadeOut";
195 document.getElementById('feedbackNo').addEventListener('click', function(e) {
197 document.getElementById('feedbackContainer').className += " fadeOut";
200 document.getElementById('feedbackContainer').addEventListener('animationend',
202 document.getElementById('feedbackContainer').style.display = 'none';
203 // Close the gap where the feedback form was.
204 var contentWrap = document.getElementById('contentWrap');
205 contentWrap.style.transition = '0.5s';
206 contentWrap.style.paddingBottom = '0px';
209 document.getElementById('contentWrap').addEventListener('transitionend',
211 var contentWrap = document.getElementById('contentWrap');
212 contentWrap.style.transition = '';
215 updateToolbarColor();
217 var pincher = (function() {
219 // When users pinch in Reader Mode, the page would zoom in or out as if it
220 // is a normal web page allowing user-zoom. At the end of pinch gesture, the
221 // page would do text reflow. These pinch-to-zoom and text reflow effects
222 // are not native, but are emulated using CSS and JavaScript.
224 // In order to achieve near-native zooming and panning frame rate, fake 3D
225 // transform is used so that the layer doesn't repaint for each frame.
227 // After the text reflow, the web content shown in the viewport should
228 // roughly be the same paragraph before zooming.
230 // The control point of font size is the html element, so that both "em" and
231 // "rem" are adjusted.
233 // TODO(wychen): Improve scroll position when elementFromPoint is body.
235 var pinching = false;
236 var fontSizeAnchor = 1.0;
238 var focusElement = null;
242 var clampedScale = 1;
251 // The zooming speed relative to pinching speed.
252 var FONT_SCALE_MULTIPLIER = 0.5;
253 var MIN_SPAN_LENGTH = 20;
255 // The font size is guaranteed to be in px.
257 parseFloat(getComputedStyle(document.documentElement).fontSize);
259 var refreshTransform = function() {
260 var slowedScale = Math.exp(Math.log(scale) * FONT_SCALE_MULTIPLIER);
261 clampedScale = Math.max(0.4, Math.min(2.5, fontSizeAnchor * slowedScale));
263 // Use "fake" 3D transform so that the layer is not repainted.
264 // With 2D transform, the frame rate would be much lower.
265 document.body.style.transform =
266 'translate3d(' + shiftX + 'px,' +
267 shiftY + 'px, 0px)' +
268 'scale(' + clampedScale/fontSizeAnchor + ')';
271 function endPinch() {
274 document.body.style.transformOrigin = '';
275 document.body.style.transform = '';
276 document.documentElement.style.fontSize = clampedScale * baseSize + "px";
278 var rect = focusElement.getBoundingClientRect();
279 var targetTop = focusPos * (rect.bottom - rect.top) + rect.top +
280 document.body.scrollTop - (initClientMid.y + shiftY);
281 document.body.scrollTop = targetTop;
284 function touchSpan(e) {
285 var count = e.touches.length;
286 var mid = touchClientMid(e);
288 for (var i = 0; i < count; i++) {
289 var dx = (e.touches[i].clientX - mid.x);
290 var dy = (e.touches[i].clientY - mid.y);
291 sum += Math.hypot(dx, dy);
293 // Avoid very small span.
294 return Math.max(MIN_SPAN_LENGTH, sum/count);
297 function touchClientMid(e) {
298 var count = e.touches.length;
301 for (var i = 0; i < count; i++) {
302 sumX += e.touches[i].clientX;
303 sumY += e.touches[i].clientY;
305 return {x: sumX/count, y: sumY/count};
308 function touchPageMid(e) {
309 var clientMid = touchClientMid(e);
310 return {x: clientMid.x - e.touches[0].clientX + e.touches[0].pageX,
311 y: clientMid.y - e.touches[0].clientY + e.touches[0].pageY};
315 handleTouchStart: function(e) {
316 if (e.touches.length < 2) return;
319 var span = touchSpan(e);
320 var clientMid = touchClientMid(e);
322 if (e.touches.length > 2) {
324 lastClientMid = clientMid;
335 parseFloat(getComputedStyle(document.documentElement).fontSize) /
338 var pinchOrigin = touchPageMid(e);
339 document.body.style.transformOrigin =
340 pinchOrigin.x + 'px ' + pinchOrigin.y + 'px';
342 // Try to preserve the pinching center after text reflow.
343 // This is accurate to the HTML element level.
344 focusElement = document.elementFromPoint(clientMid.x, clientMid.y);
345 var rect = focusElement.getBoundingClientRect();
346 initClientMid = clientMid;
347 focusPos = (initClientMid.y - rect.top) / (rect.bottom - rect.top);
350 lastClientMid = clientMid;
355 handleTouchMove: function(e) {
356 if (!pinching) return;
357 if (e.touches.length < 2) return;
360 var span = touchSpan(e);
361 var clientMid = touchClientMid(e);
363 scale *= touchSpan(e) / lastSpan;
364 shiftX += clientMid.x - lastClientMid.x;
365 shiftY += clientMid.y - lastClientMid.y;
370 lastClientMid = clientMid;
373 handleTouchEnd: function(e) {
374 if (!pinching) return;
377 var span = touchSpan(e);
378 var clientMid = touchClientMid(e);
380 if (e.touches.length >= 2) {
382 lastClientMid = clientMid;
390 handleTouchCancel: function(e) {
399 document.documentElement.style.fontSize = clampedScale * baseSize + "px";
405 clampedScale: clampedScale,
413 window.addEventListener('touchstart', pincher.handleTouchStart, false);
414 window.addEventListener('touchmove', pincher.handleTouchMove, false);
415 window.addEventListener('touchend', pincher.handleTouchEnd, false);
416 window.addEventListener('touchcancel', pincher.handleTouchCancel, false);