clipboard.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. /*!
  2. * clipboard.js v1.5.15
  3. * https://zenorocha.github.io/clipboard.js
  4. *
  5. * Licensed MIT © Zeno Rocha
  6. */
  7. (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Clipboard = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
  8. /**
  9. * A polyfill for Element.matches()
  10. */
  11. if (Element && !Element.prototype.matches) {
  12. var proto = Element.prototype;
  13. proto.matches = proto.matchesSelector ||
  14. proto.mozMatchesSelector ||
  15. proto.msMatchesSelector ||
  16. proto.oMatchesSelector ||
  17. proto.webkitMatchesSelector;
  18. }
  19. /**
  20. * Finds the closest parent that matches a selector.
  21. *
  22. * @param {Element} element
  23. * @param {String} selector
  24. * @return {Function}
  25. */
  26. function closest (element, selector) {
  27. while (element && element !== document) {
  28. if (element.matches(selector)) return element;
  29. element = element.parentNode;
  30. }
  31. }
  32. module.exports = closest;
  33. },{}],2:[function(require,module,exports){
  34. var closest = require('./closest');
  35. /**
  36. * Delegates event to a selector.
  37. *
  38. * @param {Element} element
  39. * @param {String} selector
  40. * @param {String} type
  41. * @param {Function} callback
  42. * @param {Boolean} useCapture
  43. * @return {Object}
  44. */
  45. function delegate(element, selector, type, callback, useCapture) {
  46. var listenerFn = listener.apply(this, arguments);
  47. element.addEventListener(type, listenerFn, useCapture);
  48. return {
  49. destroy: function() {
  50. element.removeEventListener(type, listenerFn, useCapture);
  51. }
  52. }
  53. }
  54. /**
  55. * Finds closest match and invokes callback.
  56. *
  57. * @param {Element} element
  58. * @param {String} selector
  59. * @param {String} type
  60. * @param {Function} callback
  61. * @return {Function}
  62. */
  63. function listener(element, selector, type, callback) {
  64. return function(e) {
  65. e.delegateTarget = closest(e.target, selector);
  66. if (e.delegateTarget) {
  67. callback.call(element, e);
  68. }
  69. }
  70. }
  71. module.exports = delegate;
  72. },{"./closest":1}],3:[function(require,module,exports){
  73. /**
  74. * Check if argument is a HTML element.
  75. *
  76. * @param {Object} value
  77. * @return {Boolean}
  78. */
  79. exports.node = function(value) {
  80. return value !== undefined
  81. && value instanceof HTMLElement
  82. && value.nodeType === 1;
  83. };
  84. /**
  85. * Check if argument is a list of HTML elements.
  86. *
  87. * @param {Object} value
  88. * @return {Boolean}
  89. */
  90. exports.nodeList = function(value) {
  91. var type = Object.prototype.toString.call(value);
  92. return value !== undefined
  93. && (type === '[object NodeList]' || type === '[object HTMLCollection]')
  94. && ('length' in value)
  95. && (value.length === 0 || exports.node(value[0]));
  96. };
  97. /**
  98. * Check if argument is a string.
  99. *
  100. * @param {Object} value
  101. * @return {Boolean}
  102. */
  103. exports.string = function(value) {
  104. return typeof value === 'string'
  105. || value instanceof String;
  106. };
  107. /**
  108. * Check if argument is a function.
  109. *
  110. * @param {Object} value
  111. * @return {Boolean}
  112. */
  113. exports.fn = function(value) {
  114. var type = Object.prototype.toString.call(value);
  115. return type === '[object Function]';
  116. };
  117. },{}],4:[function(require,module,exports){
  118. var is = require('./is');
  119. var delegate = require('delegate');
  120. /**
  121. * Validates all params and calls the right
  122. * listener function based on its target type.
  123. *
  124. * @param {String|HTMLElement|HTMLCollection|NodeList} target
  125. * @param {String} type
  126. * @param {Function} callback
  127. * @return {Object}
  128. */
  129. function listen(target, type, callback) {
  130. if (!target && !type && !callback) {
  131. throw new Error('Missing required arguments');
  132. }
  133. if (!is.string(type)) {
  134. throw new TypeError('Second argument must be a String');
  135. }
  136. if (!is.fn(callback)) {
  137. throw new TypeError('Third argument must be a Function');
  138. }
  139. if (is.node(target)) {
  140. return listenNode(target, type, callback);
  141. }
  142. else if (is.nodeList(target)) {
  143. return listenNodeList(target, type, callback);
  144. }
  145. else if (is.string(target)) {
  146. return listenSelector(target, type, callback);
  147. }
  148. else {
  149. throw new TypeError('First argument must be a String, HTMLElement, HTMLCollection, or NodeList');
  150. }
  151. }
  152. /**
  153. * Adds an event listener to a HTML element
  154. * and returns a remove listener function.
  155. *
  156. * @param {HTMLElement} node
  157. * @param {String} type
  158. * @param {Function} callback
  159. * @return {Object}
  160. */
  161. function listenNode(node, type, callback) {
  162. node.addEventListener(type, callback);
  163. return {
  164. destroy: function() {
  165. node.removeEventListener(type, callback);
  166. }
  167. }
  168. }
  169. /**
  170. * Add an event listener to a list of HTML elements
  171. * and returns a remove listener function.
  172. *
  173. * @param {NodeList|HTMLCollection} nodeList
  174. * @param {String} type
  175. * @param {Function} callback
  176. * @return {Object}
  177. */
  178. function listenNodeList(nodeList, type, callback) {
  179. Array.prototype.forEach.call(nodeList, function(node) {
  180. node.addEventListener(type, callback);
  181. });
  182. return {
  183. destroy: function() {
  184. Array.prototype.forEach.call(nodeList, function(node) {
  185. node.removeEventListener(type, callback);
  186. });
  187. }
  188. }
  189. }
  190. /**
  191. * Add an event listener to a selector
  192. * and returns a remove listener function.
  193. *
  194. * @param {String} selector
  195. * @param {String} type
  196. * @param {Function} callback
  197. * @return {Object}
  198. */
  199. function listenSelector(selector, type, callback) {
  200. return delegate(document.body, selector, type, callback);
  201. }
  202. module.exports = listen;
  203. },{"./is":3,"delegate":2}],5:[function(require,module,exports){
  204. function select(element) {
  205. var selectedText;
  206. if (element.nodeName === 'SELECT') {
  207. element.focus();
  208. selectedText = element.value;
  209. }
  210. else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
  211. element.focus();
  212. element.setSelectionRange(0, element.value.length);
  213. selectedText = element.value;
  214. }
  215. else {
  216. if (element.hasAttribute('contenteditable')) {
  217. element.focus();
  218. }
  219. var selection = window.getSelection();
  220. var range = document.createRange();
  221. range.selectNodeContents(element);
  222. selection.removeAllRanges();
  223. selection.addRange(range);
  224. selectedText = selection.toString();
  225. }
  226. return selectedText;
  227. }
  228. module.exports = select;
  229. },{}],6:[function(require,module,exports){
  230. function E () {
  231. // Keep this empty so it's easier to inherit from
  232. // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
  233. }
  234. E.prototype = {
  235. on: function (name, callback, ctx) {
  236. var e = this.e || (this.e = {});
  237. (e[name] || (e[name] = [])).push({
  238. fn: callback,
  239. ctx: ctx
  240. });
  241. return this;
  242. },
  243. once: function (name, callback, ctx) {
  244. var self = this;
  245. function listener () {
  246. self.off(name, listener);
  247. callback.apply(ctx, arguments);
  248. };
  249. listener._ = callback
  250. return this.on(name, listener, ctx);
  251. },
  252. emit: function (name) {
  253. var data = [].slice.call(arguments, 1);
  254. var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
  255. var i = 0;
  256. var len = evtArr.length;
  257. for (i; i < len; i++) {
  258. evtArr[i].fn.apply(evtArr[i].ctx, data);
  259. }
  260. return this;
  261. },
  262. off: function (name, callback) {
  263. var e = this.e || (this.e = {});
  264. var evts = e[name];
  265. var liveEvents = [];
  266. if (evts && callback) {
  267. for (var i = 0, len = evts.length; i < len; i++) {
  268. if (evts[i].fn !== callback && evts[i].fn._ !== callback)
  269. liveEvents.push(evts[i]);
  270. }
  271. }
  272. // Remove event from queue to prevent memory leak
  273. // Suggested by https://github.com/lazd
  274. // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
  275. (liveEvents.length)
  276. ? e[name] = liveEvents
  277. : delete e[name];
  278. return this;
  279. }
  280. };
  281. module.exports = E;
  282. },{}],7:[function(require,module,exports){
  283. (function (global, factory) {
  284. if (typeof define === "function" && define.amd) {
  285. define(['module', 'select'], factory);
  286. } else if (typeof exports !== "undefined") {
  287. factory(module, require('select'));
  288. } else {
  289. var mod = {
  290. exports: {}
  291. };
  292. factory(mod, global.select);
  293. global.clipboardAction = mod.exports;
  294. }
  295. })(this, function (module, _select) {
  296. 'use strict';
  297. var _select2 = _interopRequireDefault(_select);
  298. function _interopRequireDefault(obj) {
  299. return obj && obj.__esModule ? obj : {
  300. default: obj
  301. };
  302. }
  303. var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
  304. return typeof obj;
  305. } : function (obj) {
  306. return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
  307. };
  308. function _classCallCheck(instance, Constructor) {
  309. if (!(instance instanceof Constructor)) {
  310. throw new TypeError("Cannot call a class as a function");
  311. }
  312. }
  313. var _createClass = function () {
  314. function defineProperties(target, props) {
  315. for (var i = 0; i < props.length; i++) {
  316. var descriptor = props[i];
  317. descriptor.enumerable = descriptor.enumerable || false;
  318. descriptor.configurable = true;
  319. if ("value" in descriptor) descriptor.writable = true;
  320. Object.defineProperty(target, descriptor.key, descriptor);
  321. }
  322. }
  323. return function (Constructor, protoProps, staticProps) {
  324. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  325. if (staticProps) defineProperties(Constructor, staticProps);
  326. return Constructor;
  327. };
  328. }();
  329. var ClipboardAction = function () {
  330. /**
  331. * @param {Object} options
  332. */
  333. function ClipboardAction(options) {
  334. _classCallCheck(this, ClipboardAction);
  335. this.resolveOptions(options);
  336. this.initSelection();
  337. }
  338. /**
  339. * Defines base properties passed from constructor.
  340. * @param {Object} options
  341. */
  342. _createClass(ClipboardAction, [{
  343. key: 'resolveOptions',
  344. value: function resolveOptions() {
  345. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  346. this.action = options.action;
  347. this.emitter = options.emitter;
  348. this.target = options.target;
  349. this.text = options.text;
  350. this.trigger = options.trigger;
  351. this.selectedText = '';
  352. }
  353. }, {
  354. key: 'initSelection',
  355. value: function initSelection() {
  356. if (this.text) {
  357. this.selectFake();
  358. } else if (this.target) {
  359. this.selectTarget();
  360. }
  361. }
  362. }, {
  363. key: 'selectFake',
  364. value: function selectFake() {
  365. var _this = this;
  366. var isRTL = document.documentElement.getAttribute('dir') == 'rtl';
  367. this.removeFake();
  368. this.fakeHandlerCallback = function () {
  369. return _this.removeFake();
  370. };
  371. this.fakeHandler = document.body.addEventListener('click', this.fakeHandlerCallback) || true;
  372. this.fakeElem = document.createElement('textarea');
  373. // Prevent zooming on iOS
  374. this.fakeElem.style.fontSize = '12pt';
  375. // Reset box model
  376. this.fakeElem.style.border = '0';
  377. this.fakeElem.style.padding = '0';
  378. this.fakeElem.style.margin = '0';
  379. // Move element out of screen horizontally
  380. this.fakeElem.style.position = 'absolute';
  381. this.fakeElem.style[isRTL ? 'right' : 'left'] = '-9999px';
  382. // Move element to the same position vertically
  383. var yPosition = window.pageYOffset || document.documentElement.scrollTop;
  384. this.fakeElem.addEventListener('focus', window.scrollTo(0, yPosition));
  385. this.fakeElem.style.top = yPosition + 'px';
  386. this.fakeElem.setAttribute('readonly', '');
  387. this.fakeElem.value = this.text;
  388. document.body.appendChild(this.fakeElem);
  389. this.selectedText = (0, _select2.default)(this.fakeElem);
  390. this.copyText();
  391. }
  392. }, {
  393. key: 'removeFake',
  394. value: function removeFake() {
  395. if (this.fakeHandler) {
  396. document.body.removeEventListener('click', this.fakeHandlerCallback);
  397. this.fakeHandler = null;
  398. this.fakeHandlerCallback = null;
  399. }
  400. if (this.fakeElem) {
  401. document.body.removeChild(this.fakeElem);
  402. this.fakeElem = null;
  403. }
  404. }
  405. }, {
  406. key: 'selectTarget',
  407. value: function selectTarget() {
  408. this.selectedText = (0, _select2.default)(this.target);
  409. this.copyText();
  410. }
  411. }, {
  412. key: 'copyText',
  413. value: function copyText() {
  414. var succeeded = void 0;
  415. try {
  416. succeeded = document.execCommand(this.action);
  417. } catch (err) {
  418. succeeded = false;
  419. }
  420. this.handleResult(succeeded);
  421. }
  422. }, {
  423. key: 'handleResult',
  424. value: function handleResult(succeeded) {
  425. this.emitter.emit(succeeded ? 'success' : 'error', {
  426. action: this.action,
  427. text: this.selectedText,
  428. trigger: this.trigger,
  429. clearSelection: this.clearSelection.bind(this)
  430. });
  431. }
  432. }, {
  433. key: 'clearSelection',
  434. value: function clearSelection() {
  435. if (this.target) {
  436. this.target.blur();
  437. }
  438. window.getSelection().removeAllRanges();
  439. }
  440. }, {
  441. key: 'destroy',
  442. value: function destroy() {
  443. this.removeFake();
  444. }
  445. }, {
  446. key: 'action',
  447. set: function set() {
  448. var action = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'copy';
  449. this._action = action;
  450. if (this._action !== 'copy' && this._action !== 'cut') {
  451. throw new Error('Invalid "action" value, use either "copy" or "cut"');
  452. }
  453. },
  454. get: function get() {
  455. return this._action;
  456. }
  457. }, {
  458. key: 'target',
  459. set: function set(target) {
  460. if (target !== undefined) {
  461. if (target && (typeof target === 'undefined' ? 'undefined' : _typeof(target)) === 'object' && target.nodeType === 1) {
  462. if (this.action === 'copy' && target.hasAttribute('disabled')) {
  463. throw new Error('Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
  464. }
  465. if (this.action === 'cut' && (target.hasAttribute('readonly') || target.hasAttribute('disabled'))) {
  466. throw new Error('Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes');
  467. }
  468. this._target = target;
  469. } else {
  470. throw new Error('Invalid "target" value, use a valid Element');
  471. }
  472. }
  473. },
  474. get: function get() {
  475. return this._target;
  476. }
  477. }]);
  478. return ClipboardAction;
  479. }();
  480. module.exports = ClipboardAction;
  481. });
  482. },{"select":5}],8:[function(require,module,exports){
  483. (function (global, factory) {
  484. if (typeof define === "function" && define.amd) {
  485. define(['module', './clipboard-action', 'tiny-emitter', 'good-listener'], factory);
  486. } else if (typeof exports !== "undefined") {
  487. factory(module, require('./clipboard-action'), require('tiny-emitter'), require('good-listener'));
  488. } else {
  489. var mod = {
  490. exports: {}
  491. };
  492. factory(mod, global.clipboardAction, global.tinyEmitter, global.goodListener);
  493. global.clipboard = mod.exports;
  494. }
  495. })(this, function (module, _clipboardAction, _tinyEmitter, _goodListener) {
  496. 'use strict';
  497. var _clipboardAction2 = _interopRequireDefault(_clipboardAction);
  498. var _tinyEmitter2 = _interopRequireDefault(_tinyEmitter);
  499. var _goodListener2 = _interopRequireDefault(_goodListener);
  500. function _interopRequireDefault(obj) {
  501. return obj && obj.__esModule ? obj : {
  502. default: obj
  503. };
  504. }
  505. function _classCallCheck(instance, Constructor) {
  506. if (!(instance instanceof Constructor)) {
  507. throw new TypeError("Cannot call a class as a function");
  508. }
  509. }
  510. var _createClass = function () {
  511. function defineProperties(target, props) {
  512. for (var i = 0; i < props.length; i++) {
  513. var descriptor = props[i];
  514. descriptor.enumerable = descriptor.enumerable || false;
  515. descriptor.configurable = true;
  516. if ("value" in descriptor) descriptor.writable = true;
  517. Object.defineProperty(target, descriptor.key, descriptor);
  518. }
  519. }
  520. return function (Constructor, protoProps, staticProps) {
  521. if (protoProps) defineProperties(Constructor.prototype, protoProps);
  522. if (staticProps) defineProperties(Constructor, staticProps);
  523. return Constructor;
  524. };
  525. }();
  526. function _possibleConstructorReturn(self, call) {
  527. if (!self) {
  528. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  529. }
  530. return call && (typeof call === "object" || typeof call === "function") ? call : self;
  531. }
  532. function _inherits(subClass, superClass) {
  533. if (typeof superClass !== "function" && superClass !== null) {
  534. throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  535. }
  536. subClass.prototype = Object.create(superClass && superClass.prototype, {
  537. constructor: {
  538. value: subClass,
  539. enumerable: false,
  540. writable: true,
  541. configurable: true
  542. }
  543. });
  544. if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
  545. }
  546. var Clipboard = function (_Emitter) {
  547. _inherits(Clipboard, _Emitter);
  548. /**
  549. * @param {String|HTMLElement|HTMLCollection|NodeList} trigger
  550. * @param {Object} options
  551. */
  552. function Clipboard(trigger, options) {
  553. _classCallCheck(this, Clipboard);
  554. var _this = _possibleConstructorReturn(this, (Clipboard.__proto__ || Object.getPrototypeOf(Clipboard)).call(this));
  555. _this.resolveOptions(options);
  556. _this.listenClick(trigger);
  557. return _this;
  558. }
  559. /**
  560. * Defines if attributes would be resolved using internal setter functions
  561. * or custom functions that were passed in the constructor.
  562. * @param {Object} options
  563. */
  564. _createClass(Clipboard, [{
  565. key: 'resolveOptions',
  566. value: function resolveOptions() {
  567. var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  568. this.action = typeof options.action === 'function' ? options.action : this.defaultAction;
  569. this.target = typeof options.target === 'function' ? options.target : this.defaultTarget;
  570. this.text = typeof options.text === 'function' ? options.text : this.defaultText;
  571. }
  572. }, {
  573. key: 'listenClick',
  574. value: function listenClick(trigger) {
  575. var _this2 = this;
  576. this.listener = (0, _goodListener2.default)(trigger, 'click', function (e) {
  577. return _this2.onClick(e);
  578. });
  579. }
  580. }, {
  581. key: 'onClick',
  582. value: function onClick(e) {
  583. var trigger = e.delegateTarget || e.currentTarget;
  584. if (this.clipboardAction) {
  585. this.clipboardAction = null;
  586. }
  587. this.clipboardAction = new _clipboardAction2.default({
  588. action: this.action(trigger),
  589. target: this.target(trigger),
  590. text: this.text(trigger),
  591. trigger: trigger,
  592. emitter: this
  593. });
  594. }
  595. }, {
  596. key: 'defaultAction',
  597. value: function defaultAction(trigger) {
  598. return getAttributeValue('action', trigger);
  599. }
  600. }, {
  601. key: 'defaultTarget',
  602. value: function defaultTarget(trigger) {
  603. var selector = getAttributeValue('target', trigger);
  604. if (selector) {
  605. return document.querySelector(selector);
  606. }
  607. }
  608. }, {
  609. key: 'defaultText',
  610. value: function defaultText(trigger) {
  611. return getAttributeValue('text', trigger);
  612. }
  613. }, {
  614. key: 'destroy',
  615. value: function destroy() {
  616. this.listener.destroy();
  617. if (this.clipboardAction) {
  618. this.clipboardAction.destroy();
  619. this.clipboardAction = null;
  620. }
  621. }
  622. }]);
  623. return Clipboard;
  624. }(_tinyEmitter2.default);
  625. /**
  626. * Helper function to retrieve attribute value.
  627. * @param {String} suffix
  628. * @param {Element} element
  629. */
  630. function getAttributeValue(suffix, element) {
  631. var attribute = 'data-clipboard-' + suffix;
  632. if (!element.hasAttribute(attribute)) {
  633. return;
  634. }
  635. return element.getAttribute(attribute);
  636. }
  637. module.exports = Clipboard;
  638. });
  639. },{"./clipboard-action":7,"good-listener":4,"tiny-emitter":6}]},{},[8])(8)
  640. });