暫無描述

utils.js 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. 'use strict';
  2. import { Dimensions, Platform } from 'react-native';
  3. const domMutationObserveScript = `
  4. var MutationObserver =
  5. window.MutationObserver || window.WebKitMutationObserver;
  6. var observer = new MutationObserver(updateSize);
  7. observer.observe(document, {
  8. subtree: true,
  9. attributes: true
  10. });
  11. `;
  12. const updateSizeWithMessage = (element, scalesPageToFit) =>
  13. `
  14. var lastHeight = 0;
  15. var heightTheSameTimes = 0;
  16. var maxHeightTheSameTimes = 5;
  17. var forceRefreshDelay = 1000;
  18. var forceRefreshTimeout;
  19. var checkPostMessageTimeout;
  20. function updateSize(event) {
  21. if (
  22. !window.hasOwnProperty('ReactNativeWebView') ||
  23. !window.ReactNativeWebView.hasOwnProperty('postMessage')
  24. ) {
  25. checkPostMessageTimeout = setTimeout(updateSize, 200);
  26. return;
  27. }
  28. clearTimeout(checkPostMessageTimeout);
  29. height = ${element}.offsetHeight || document.documentElement.offsetHeight;
  30. width = ${element}.offsetWidth || document.documentElement.offsetWidth;
  31. var scale = ${scalesPageToFit ? 'screen.width / window.innerWidth' : '1'};
  32. window.ReactNativeWebView.postMessage(JSON.stringify({ width: Math.min(width, screen.width), height: height * scale }));
  33. // Make additional height checks (required to fix issues wit twitter embeds)
  34. clearTimeout(forceRefreshTimeout);
  35. if (lastHeight !== height) {
  36. heightTheSameTimes = 1;
  37. } else {
  38. heightTheSameTimes++;
  39. }
  40. lastHeight = height;
  41. if (heightTheSameTimes <= maxHeightTheSameTimes) {
  42. forceRefreshTimeout = setTimeout(
  43. updateSize,
  44. heightTheSameTimes * forceRefreshDelay
  45. );
  46. }
  47. }
  48. `;
  49. // add viewport setting to meta
  50. const makeScalePageToFit = (zoomable, scalesPageToFit) =>
  51. scalesPageToFit || Platform.OS === 'android'
  52. ? ''
  53. : `
  54. var meta = document.createElement("meta");
  55. meta.setAttribute("name", "viewport");
  56. meta.setAttribute("content", "width=device-width, user-scalable=${zoomable ? 'yes' : 'no'}");
  57. document.getElementsByTagName("head")[0].appendChild(meta);
  58. `;
  59. const getBaseScript = ({ style, zoomable, scalesPageToFit }) =>
  60. `
  61. ;
  62. if (!document.getElementById("rnahw-wrapper")) {
  63. var wrapper = document.createElement('div');
  64. wrapper.id = 'rnahw-wrapper';
  65. while (document.body.firstChild instanceof Node) {
  66. wrapper.appendChild(document.body.firstChild);
  67. }
  68. document.body.appendChild(wrapper);
  69. }
  70. ${updateSizeWithMessage('wrapper', scalesPageToFit)}
  71. window.addEventListener('load', updateSize);
  72. window.addEventListener('resize', updateSize);
  73. ${domMutationObserveScript}
  74. ${makeScalePageToFit(zoomable, scalesPageToFit)}
  75. updateSize();
  76. `;
  77. const appendFilesToHead = ({ files, script }) =>
  78. files.reduceRight((combinedScript, file) => {
  79. const { rel, type, href } = file;
  80. return `
  81. var link = document.createElement('link');
  82. link.rel = '${rel}';
  83. link.type = '${type}';
  84. link.href = '${href}';
  85. document.head.appendChild(link);
  86. ${combinedScript}
  87. `;
  88. }, script);
  89. const screenWidth = Dimensions.get('window').width;
  90. const bodyStyle = `
  91. body {
  92. margin: 0;
  93. padding: 0;
  94. }
  95. `;
  96. const appendStylesToHead = ({ style, script }) => {
  97. const currentStyles = style ? bodyStyle + style : bodyStyle;
  98. // Escape any single quotes or newlines in the CSS with .replace()
  99. const escaped = currentStyles.replace(/\'/g, "\\'").replace(/\n/g, '\\n');
  100. return `
  101. var styleElement = document.createElement('style');
  102. styleElement.innerHTML = '${escaped}';
  103. document.head.appendChild(styleElement);
  104. ${script}
  105. `;
  106. };
  107. const getInjectedSource = ({ html, script }) => `
  108. ${html}
  109. <script>
  110. // prevents code colissions with global scope
  111. (function() {
  112. ${script}
  113. })();
  114. </script>
  115. `;
  116. const getScript = ({ files, customStyle, customScript, style, zoomable, scalesPageToFit }) => {
  117. let script = getBaseScript({ style, zoomable, scalesPageToFit });
  118. script = files && files.length > 0 ? appendFilesToHead({ files, script }) : script;
  119. script = appendStylesToHead({ style: customStyle, script });
  120. customScript && (script = customScript + script);
  121. return script;
  122. };
  123. export const getWidth = style => {
  124. return style && style.width ? style.width : screenWidth;
  125. };
  126. export const isSizeChanged = ({ height, previousHeight, width, previousWidth }) => {
  127. if (!height || !width) {
  128. return;
  129. }
  130. return height !== previousHeight || width !== previousWidth;
  131. };
  132. export const reduceData = props => {
  133. const { source } = props;
  134. const script = getScript(props);
  135. const { html, baseUrl } = source;
  136. if (html) {
  137. return {
  138. currentSource: { baseUrl, html: getInjectedSource({ html, script }) }
  139. };
  140. } else {
  141. return {
  142. currentSource: source,
  143. script
  144. };
  145. }
  146. };
  147. export const shouldUpdate = ({ prevProps, nextProps }) => {
  148. if (!(prevProps && nextProps)) {
  149. return true;
  150. }
  151. for (const prop in nextProps) {
  152. if (nextProps[prop] !== prevProps[prop]) {
  153. if (typeof nextProps[prop] === 'object' && typeof prevProps[prop] === 'object') {
  154. if (
  155. shouldUpdate({
  156. prevProps: prevProps[prop],
  157. nextProps: nextProps[prop]
  158. })
  159. ) {
  160. return true;
  161. }
  162. } else {
  163. return true;
  164. }
  165. }
  166. }
  167. return false;
  168. };