utils.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. /* global Android */
  2. const html = require('choo/html');
  3. const b64 = require('base64-js');
  4. function arrayToB64(array) {
  5. return b64
  6. .fromByteArray(array)
  7. .replace(/\+/g, '-')
  8. .replace(/\//g, '_')
  9. .replace(/=/g, '');
  10. }
  11. function b64ToArray(str) {
  12. return b64.toByteArray(str + '==='.slice((str.length + 3) % 4));
  13. }
  14. function loadShim(polyfill) {
  15. return new Promise((resolve, reject) => {
  16. const shim = document.createElement('script');
  17. shim.src = polyfill;
  18. shim.addEventListener('load', () => resolve(true));
  19. shim.addEventListener('error', () => resolve(false));
  20. document.head.appendChild(shim);
  21. });
  22. }
  23. function isFile(id) {
  24. return /^[0-9a-fA-F]{10}$/.test(id);
  25. }
  26. function copyToClipboard(str) {
  27. const aux = document.createElement('input');
  28. aux.setAttribute('value', str);
  29. aux.contentEditable = true;
  30. aux.readOnly = true;
  31. document.body.appendChild(aux);
  32. if (navigator.userAgent.match(/iphone|ipad|ipod/i)) {
  33. const range = document.createRange();
  34. range.selectNodeContents(aux);
  35. const sel = getSelection();
  36. sel.removeAllRanges();
  37. sel.addRange(range);
  38. aux.setSelectionRange(0, str.length);
  39. } else {
  40. aux.select();
  41. }
  42. const result = document.execCommand('copy');
  43. document.body.removeChild(aux);
  44. return result;
  45. }
  46. const LOCALIZE_NUMBERS = !!(
  47. typeof Intl === 'object' &&
  48. Intl &&
  49. typeof Intl.NumberFormat === 'function' &&
  50. typeof navigator === 'object'
  51. );
  52. const UNITS = ['B', 'kB', 'MB', 'GB'];
  53. function bytes(num, decimalDigits = 1) {
  54. if (num < 1) {
  55. return '0B';
  56. }
  57. const exponent = Math.min(Math.floor(Math.log10(num) / 3), UNITS.length - 1);
  58. const n = Number(num / Math.pow(1024, exponent));
  59. let nStr = n.toFixed(1);
  60. if (LOCALIZE_NUMBERS) {
  61. try {
  62. const locale = document.querySelector('html').lang;
  63. nStr = n.toLocaleString(locale, {
  64. minimumFractionDigits: decimalDigits,
  65. maximumFractionDigits: decimalDigits
  66. });
  67. } catch (e) {
  68. // fall through
  69. }
  70. }
  71. return `${nStr}${UNITS[exponent]}`;
  72. }
  73. function percent(ratio) {
  74. if (LOCALIZE_NUMBERS) {
  75. try {
  76. const locale = document.querySelector('html').lang;
  77. return ratio.toLocaleString(locale, { style: 'percent' });
  78. } catch (e) {
  79. // fall through
  80. }
  81. }
  82. return `${Math.floor(ratio * 100)}%`;
  83. }
  84. function number(n) {
  85. if (LOCALIZE_NUMBERS) {
  86. const locale = document.querySelector('html').lang;
  87. return n.toLocaleString(locale);
  88. }
  89. return n.toString();
  90. }
  91. function allowedCopy() {
  92. const support = !!document.queryCommandSupported;
  93. return support ? document.queryCommandSupported('copy') : false;
  94. }
  95. function delay(delay = 100) {
  96. return new Promise(resolve => setTimeout(resolve, delay));
  97. }
  98. function fadeOut(selector) {
  99. const classes = document.querySelector(selector).classList;
  100. classes.remove('effect--fadeIn');
  101. classes.add('effect--fadeOut');
  102. return delay(300);
  103. }
  104. function openLinksInNewTab(links, should = true) {
  105. links = links || Array.from(document.querySelectorAll('a:not([target])'));
  106. if (should) {
  107. links.forEach(l => {
  108. l.setAttribute('target', '_blank');
  109. l.setAttribute('rel', 'noopener noreferrer');
  110. });
  111. } else {
  112. links.forEach(l => {
  113. l.removeAttribute('target');
  114. l.removeAttribute('rel');
  115. });
  116. }
  117. return links;
  118. }
  119. function browserName() {
  120. try {
  121. if (/firefox/i.test(navigator.userAgent)) {
  122. return 'firefox';
  123. }
  124. if (/edge/i.test(navigator.userAgent)) {
  125. return 'edge';
  126. }
  127. if (/trident/i.test(navigator.userAgent)) {
  128. return 'ie';
  129. }
  130. if (/chrome/i.test(navigator.userAgent)) {
  131. return 'chrome';
  132. }
  133. if (/safari/i.test(navigator.userAgent)) {
  134. return 'safari';
  135. }
  136. if (/send android/i.test(navigator.userAgent)) {
  137. return 'android-app';
  138. }
  139. return 'other';
  140. } catch (e) {
  141. return 'unknown';
  142. }
  143. }
  144. async function streamToArrayBuffer(stream, size) {
  145. const reader = stream.getReader();
  146. let state = await reader.read();
  147. if (size) {
  148. const result = new Uint8Array(size);
  149. let offset = 0;
  150. while (!state.done) {
  151. result.set(state.value, offset);
  152. offset += state.value.length;
  153. state = await reader.read();
  154. }
  155. return result.buffer;
  156. }
  157. const parts = [];
  158. let len = 0;
  159. while (!state.done) {
  160. parts.push(state.value);
  161. len += state.value.length;
  162. state = await reader.read();
  163. }
  164. let offset = 0;
  165. const result = new Uint8Array(len);
  166. for (const part of parts) {
  167. result.set(part, offset);
  168. offset += part.length;
  169. }
  170. return result.buffer;
  171. }
  172. function list(items, ulStyle = '', liStyle = '') {
  173. const lis = items.map(
  174. i =>
  175. html`
  176. <li class="${liStyle}">${i}</li>
  177. `
  178. );
  179. return html`
  180. <ul class="${ulStyle}">
  181. ${lis}
  182. </ul>
  183. `;
  184. }
  185. function secondsToL10nId(seconds) {
  186. if (seconds < 3600) {
  187. return { id: 'timespanMinutes', num: Math.floor(seconds / 60) };
  188. } else if (seconds < 86400) {
  189. return { id: 'timespanHours', num: Math.floor(seconds / 3600) };
  190. } else {
  191. return { id: 'timespanDays', num: Math.floor(seconds / 86400) };
  192. }
  193. }
  194. function timeLeft(milliseconds) {
  195. if (milliseconds < 1) {
  196. return { id: 'linkExpiredAlt' };
  197. }
  198. const minutes = Math.floor(milliseconds / 1000 / 60);
  199. const hours = Math.floor(minutes / 60);
  200. const days = Math.floor(hours / 24);
  201. if (days >= 1) {
  202. return {
  203. id: 'expiresDaysHoursMinutes',
  204. days,
  205. hours: hours % 24,
  206. minutes: minutes % 60
  207. };
  208. }
  209. if (hours >= 1) {
  210. return {
  211. id: 'expiresHoursMinutes',
  212. hours,
  213. minutes: minutes % 60
  214. };
  215. } else if (hours === 0) {
  216. if (minutes === 0) {
  217. return { id: 'expiresMinutes', minutes: '< 1' };
  218. }
  219. return { id: 'expiresMinutes', minutes };
  220. }
  221. return null;
  222. }
  223. function platform() {
  224. if (typeof Android === 'object') {
  225. return 'android';
  226. }
  227. return 'web';
  228. }
  229. module.exports = {
  230. fadeOut,
  231. delay,
  232. allowedCopy,
  233. bytes,
  234. percent,
  235. number,
  236. copyToClipboard,
  237. arrayToB64,
  238. b64ToArray,
  239. loadShim,
  240. isFile,
  241. openLinksInNewTab,
  242. browserName,
  243. streamToArrayBuffer,
  244. list,
  245. secondsToL10nId,
  246. timeLeft,
  247. platform
  248. };