No Description

utils.js 6.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. 'use strict';
  2. import { Dimensions } 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 usingScale = ${scalesPageToFit} ? screen.width / window.innerWidth : 1;
  15. var scaling = false;
  16. var zoomedin = false;
  17. var lastHeight = 0;
  18. var heightTheSameTimes = 0;
  19. var maxHeightTheSameTimes = 5;
  20. var forceRefreshDelay = 1000;
  21. var forceRefreshTimeout;
  22. var checkPostMessageTimeout;
  23. function updateSize() {
  24. if (zoomedin || scaling || document.fullscreenElement) {
  25. return;
  26. }
  27. if (
  28. !window.hasOwnProperty('ReactNativeWebView') ||
  29. !window.ReactNativeWebView.hasOwnProperty('postMessage')
  30. ) {
  31. checkPostMessageTimeout = setTimeout(updateSize, 200);
  32. return;
  33. }
  34. clearTimeout(checkPostMessageTimeout);
  35. height = ${element}.offsetHeight || document.documentElement.offsetHeight;
  36. width = ${element}.offsetWidth || document.documentElement.offsetWidth;
  37. window.ReactNativeWebView.postMessage(JSON.stringify({ width: Math.min(width, screen.width), height: height * usingScale }));
  38. // Make additional height checks (required to fix issues wit twitter embeds)
  39. clearTimeout(forceRefreshTimeout);
  40. if (lastHeight !== height) {
  41. heightTheSameTimes = 1;
  42. } else {
  43. heightTheSameTimes++;
  44. }
  45. lastHeight = height;
  46. if (heightTheSameTimes <= maxHeightTheSameTimes) {
  47. forceRefreshTimeout = setTimeout(
  48. updateSize,
  49. heightTheSameTimes * forceRefreshDelay
  50. );
  51. }
  52. }
  53. `;
  54. const setViewportContent = content => {
  55. if (!content) {
  56. return '';
  57. }
  58. return `
  59. var meta = document.createElement("meta");
  60. meta.setAttribute("name", "viewport");
  61. meta.setAttribute("content", "${content}");
  62. document.getElementsByTagName("head")[0].appendChild(meta);
  63. `;
  64. };
  65. const detectZoomChanged = `
  66. var latestTapStamp = 0;
  67. var lastScale = 1.0;
  68. var doubleTapDelay = 400;
  69. function detectZoomChanged() {
  70. var tempZoomedin = (screen.width / window.innerWidth) > usingScale;
  71. tempZoomedin !== zoomedin && window.ReactNativeWebView.postMessage(JSON.stringify({ zoomedin: tempZoomedin }));
  72. zoomedin = tempZoomedin;
  73. }
  74. window.addEventListener('ontouchstart', event => {
  75. if (event.touches.length === 2) {
  76. scaling = true;
  77. }
  78. })
  79. window.addEventListener('touchend', event => {
  80. if(scaling) {
  81. scaleing = false;
  82. }
  83. var tempScale = event.scale;
  84. tempScale !== lastScale && detectZoomChanged();
  85. lastScale = tempScale;
  86. var timeSince = new Date().getTime() - latestTapStamp;
  87. // double tap
  88. if(timeSince < 600 && timeSince > 0) {
  89. zoomedinTimeOut = setTimeout(() => {
  90. clearTimeout(zoomedinTimeOut);
  91. detectZoomChanged();
  92. }, doubleTapDelay);
  93. }
  94. latestTapStamp = new Date().getTime();
  95. });
  96. `;
  97. const getBaseScript = ({ viewportContent, scalesPageToFit, scrollEnabledWithZoomedin }) =>
  98. `
  99. ;
  100. if (!document.getElementById("rnahw-wrapper")) {
  101. var wrapper = document.createElement('div');
  102. wrapper.id = 'rnahw-wrapper';
  103. while (document.body.firstChild instanceof Node) {
  104. wrapper.appendChild(document.body.firstChild);
  105. }
  106. document.body.appendChild(wrapper);
  107. }
  108. ${updateSizeWithMessage('wrapper', scalesPageToFit)}
  109. window.addEventListener('load', updateSize);
  110. window.addEventListener('resize', updateSize);
  111. ${domMutationObserveScript}
  112. ${setViewportContent(viewportContent)}
  113. ${scrollEnabledWithZoomedin ? detectZoomChanged : ''}
  114. updateSize();
  115. `;
  116. const appendFilesToHead = ({ files, script }) =>
  117. files.reduceRight((combinedScript, file) => {
  118. const { rel, type, href } = file;
  119. return `
  120. var link = document.createElement('link');
  121. link.rel = '${rel}';
  122. link.type = '${type}';
  123. link.href = '${href}';
  124. document.head.appendChild(link);
  125. ${combinedScript}
  126. `;
  127. }, script);
  128. const screenWidth = Dimensions.get('window').width;
  129. const bodyStyle = `
  130. body {
  131. margin: 0;
  132. padding: 0;
  133. }
  134. `;
  135. const appendStylesToHead = ({ style, script }) => {
  136. const currentStyles = style ? bodyStyle + style : bodyStyle;
  137. // Escape any single quotes or newlines in the CSS with .replace()
  138. const escaped = currentStyles.replace(/\'/g, "\\'").replace(/\n/g, '\\n');
  139. return `
  140. var styleElement = document.createElement('style');
  141. styleElement.innerHTML = '${escaped}';
  142. document.head.appendChild(styleElement);
  143. ${script}
  144. `;
  145. };
  146. const getInjectedSource = ({ html, script }) => `
  147. ${html}
  148. <script>
  149. // prevents code colissions with global scope
  150. (function() {
  151. ${script}
  152. })();
  153. </script>
  154. `;
  155. const getScript = ({
  156. files,
  157. customStyle,
  158. customScript,
  159. style,
  160. viewportContent,
  161. scalesPageToFit,
  162. scrollEnabledWithZoomedin
  163. }) => {
  164. let script = getBaseScript({ viewportContent, scalesPageToFit, scrollEnabledWithZoomedin });
  165. script = files && files.length > 0 ? appendFilesToHead({ files, script }) : script;
  166. script = appendStylesToHead({ style: customStyle, script });
  167. customScript && (script = customScript + script);
  168. return script;
  169. };
  170. export const getWidth = style => {
  171. return style && style.width ? style.width : screenWidth;
  172. };
  173. export const isSizeChanged = ({ height, previousHeight, width, previousWidth }) => {
  174. if (!height || !width) {
  175. return;
  176. }
  177. return height !== previousHeight || width !== previousWidth;
  178. };
  179. export const reduceData = props => {
  180. const { source } = props;
  181. const script = getScript(props);
  182. const { html, baseUrl } = source;
  183. if (html) {
  184. return {
  185. currentSource: { baseUrl, html: getInjectedSource({ html, script }) }
  186. };
  187. } else {
  188. return {
  189. currentSource: source,
  190. script
  191. };
  192. }
  193. };
  194. export const shouldUpdate = ({ prevProps, nextProps }) => {
  195. if (!(prevProps && nextProps)) {
  196. return true;
  197. }
  198. for (const prop in nextProps) {
  199. if (nextProps[prop] !== prevProps[prop]) {
  200. if (typeof nextProps[prop] === 'object' && typeof prevProps[prop] === 'object') {
  201. if (
  202. shouldUpdate({
  203. prevProps: prevProps[prop],
  204. nextProps: nextProps[prop]
  205. })
  206. ) {
  207. return true;
  208. }
  209. } else {
  210. return true;
  211. }
  212. }
  213. }
  214. return false;
  215. };