Greasy Fork is available in English.

Sima-land.ru Popup Original Image Viewer/Downloader (modified version of MPIV)

показывает при наведении мышью на фото товара оригинальное фото товара без наложения названия сайта, с возможностью сохранения фото в файл нажатием клавиши "d" на клавиатуре, или открытием фото в отдельной новой вкладке клавишей "t"

  1. // ==UserScript==
  2. // @name Sima-land.ru Popup Original Image Viewer/Downloader (modified version of MPIV)
  3. // @description показывает при наведении мышью на фото товара оригинальное фото товара без наложения названия сайта, с возможностью сохранения фото в файл нажатием клавиши "d" на клавиатуре, или открытием фото в отдельной новой вкладке клавишей "t"
  4. // @important при первой загрузке файла (фото) необходимо разрешить tampermonkey сохранять файлы, появиться отдельное окно с зеленой шапкой Tampermonkey и текстом "A userscript wants to access a cross-origin resource." Вам необходимо выбрать пункт Always allow или Always allow domain..
  5. // @version 01.07.2020 alpha
  6. // @homepage http://sima-land.ru
  7. // @author itz
  8. // @include http*
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_openInTab
  13. // @grant GM_registerMenuCommand
  14. // @grant GM_setClipboard
  15. // @connect-src *
  16. // @namespace https://greatest.deepsurf.us/users/662096
  17. // ==/UserScript==
  18.  
  19. 'use strict';
  20.  
  21. var d = document, wn = window, hostname = location.hostname, trusted = ['greatest.deepsurf.us', 'w9p.co'], imgtab = d.images.length == 1 && d.images[0].parentNode == d.body && !d.links.length, cfg = loadCfg(), enabled = cfg.imgtab || !imgtab, _ = {}, hosts;
  22.  
  23. function loadCfg() {
  24. return fixCfg(GM_getValue('cfg'), true);
  25. }
  26.  
  27. function fixCfg(s, save) {
  28. var cfg, def = {
  29. version: 5,
  30. delay: 500,
  31. start: 'auto',
  32. zoom: 'context',
  33. center: false,
  34. imgtab: false,
  35. close: true,
  36. preload: false,
  37. css: '',
  38. scales: [],
  39. hosts: '',
  40. scale: 1.5,
  41. xhr: true
  42. };
  43. try { cfg = JSON.parse(s); } catch(ex) {}
  44. if(typeof cfg != 'object' || !cfg) cfg = {}; else if(cfg.version == def.version) return cfg;
  45. for(var dp in def) {
  46. if(def.hasOwnProperty(dp) && typeof cfg[dp] != typeof def[dp]) cfg[dp] = def[dp];
  47. }
  48. if(cfg.version == 3 && cfg.scales[0] === 0) cfg.scales[0] = '0!';
  49. for(var cp in cfg) {
  50. if(!def.hasOwnProperty(cp)) delete cfg[cp];
  51. }
  52. cfg.version = def.version;
  53. if(save) saveCfg(cfg);
  54. return cfg;
  55. }
  56.  
  57. function saveCfg(newCfg) {
  58. GM_setValue('cfg', JSON.stringify(cfg = newCfg));
  59. }
  60.  
  61. function loadHosts() {
  62. var hosts = [
  63.  
  64. ];
  65. if(cfg.hosts) {
  66. var lines = cfg.hosts.split(/[\r\n]+/);
  67. for(var i = lines.length, s; i-- && (s = lines[i]);) {
  68. try {
  69. var h = JSON.parse(s);
  70. if(h.r) h.r = new RegExp(h.r, 'i');
  71. if(h.s && typeof h.s == 'string' && contains(h.s, 'return ')) h.s = new Function('m', 'node', h.s);
  72. if(h.q && typeof h.q == 'string' && contains(h.q, 'return ')) h.q = new Function('text', 'doc', 'node', h.q);
  73. if(contains(h.c, 'return ')) h.c = new Function('text', 'doc', 'node', h.c);
  74. hosts.splice(0, 0, h);
  75. } catch(ex) {
  76. handleError('Host rule invalid: ' + s);
  77. }
  78. }
  79. }
  80. return hosts.filter(function(h) { return !h.d || contains(hostname, h.d); });
  81. }
  82.  
  83. function onMouseOver(e) {
  84. if(!enabled || e.shiftKey || _.zoom || !activate(e.target, e.ctrlKey)) return;
  85. updateMouse(e);
  86. if(e.ctrlKey) {
  87. startPopup();
  88. } else if(cfg.start == 'auto' && !_.manual) {
  89. if(cfg.preload) {
  90. _.preloadStart = Date.now();
  91. startPopup();
  92. setStatus('preloading', 'add');
  93. } else {
  94. _.timeout = wn.setTimeout(startPopup, cfg.delay);
  95. }
  96. if(cfg.preload) wn.setTimeout(function() { setStatus('preloading', 'remove'); }, cfg.delay);
  97. }
  98. else
  99. setStatus('ready');
  100. }
  101.  
  102. function onMouseOut(e) {
  103. if(!e.relatedTarget && !e.shiftKey) deactivate();
  104. }
  105.  
  106. function onMouseMove(e) {
  107. updateMouse(e);
  108. if(e.shiftKey) return (_.lazyUnload = true);
  109. if(!_.zoomed && !_.cr) return deactivate();
  110. if(_.zoom) {
  111. placePopup();
  112. var bx = _.view.width/6, by = _.view.height/6;
  113. setStatus('edge', _.cx < bx || _.cx > _.view.width - bx || _.cy < by || _.cy > _.view.height - by ? 'add' : 'remove');
  114. }
  115. }
  116.  
  117. function onMouseDown(e) {
  118. if(e.which != 3 && !e.shiftKey) deactivate(true);
  119. else if(e.shiftKey && e.which == 1 && _.popup && _.popup.controls) _.controlled = _.zoomed = true;
  120. }
  121.  
  122. function onMouseScroll(e) {
  123. var dir = (e.deltaY || -e.wheelDelta) > 0 ? 1 : -1;
  124. if(_.zoom) {
  125. drop(e);
  126. var idx = _.scales.indexOf(_.scale);
  127. idx -= dir;
  128. if(idx >= 0 && idx < _.scales.length) _.scale = _.scales[idx];
  129. if(idx == 0 && cfg.close) {
  130. if(!_.gItems || _.gItems.length < 2) return deactivate(true);
  131. _.zoom = false;
  132. showFileInfo();
  133. }
  134. if(_.zooming) _.popup.classList.add('mpiv-zooming');
  135. placePopup();
  136. updateTitle();
  137. } else if(_.gItems && _.gItems.length > 1 && _.popup) {
  138. drop(e);
  139. nextGalleryItem(dir);
  140. } else if(cfg.zoom == 'wheel' && dir < 0 && _.popup) {
  141. drop(e);
  142. toggleZoom();
  143. } else {
  144. deactivate();
  145. }
  146. }
  147.  
  148. function onKeyDown(e) {
  149. if(e.keyCode == 16) {
  150. setStatus('shift', 'add');
  151. if(_.popup && 'controls' in _.popup) _.popup.controls = true;
  152. } else if(e.keyCode == 17 && (cfg.start != 'auto' || _.manual) && !_.popup) {
  153. startPopup();
  154. }
  155. }
  156.  
  157. function onKeyUp(e) {
  158. switch(e.keyCode) {
  159. case 16:
  160. setStatus('shift', 'remove');
  161. if(_.popup.controls) _.popup.controls = false;
  162. if(_.controlled) return _.controlled = false;
  163. _.popup && (_.zoomed || !('cr' in _) || _.cr) ? toggleZoom() : deactivate(true);
  164. break;
  165. case 17:
  166. break;
  167. case 27:
  168. deactivate(true);
  169. break;
  170. case 39:
  171. case 74:
  172. drop(e);
  173. nextGalleryItem(1);
  174. break;
  175. case 37:
  176. case 75:
  177. drop(e);
  178. nextGalleryItem(-1);
  179. break;
  180. case 68:
  181. drop(e);
  182. var name = (_.iurl || _.popup.src).split('/').pop().replace(/[:#\?].*/, '');
  183. if(!contains(name, '.')) name += '.jpg';
  184. saveFile(_.popup.src, name, function() { setBar('Could not download ' + name + '.', 'error'); });
  185. break;
  186. case 84:
  187. _.lazyUnload = true;
  188. if(_.tabfix && !_.xhr && tag(_.popup) == 'IMG' && contains(navigator.userAgent, 'Gecko/'))
  189. GM_openInTab('data:text/html;,' + encodeURIComponent('<html><head><style>body{margin:0;padding:0;background:#222}.fit{overflow:hidden}.fit>img{max-width:100vw;max-height:100vh}body>img{margin:auto;position:absolute;left:0;right:0;top:0;bottom:0}</style></head><body class="fit"><img onclick="document.body.classList.toggle(\'fit\')" src="' + _.popup.src + '"></body></html>'));
  190. else
  191. GM_openInTab(_.popup.src);
  192. deactivate();
  193. break;
  194. default:
  195. deactivate(true);
  196. }
  197.  
  198. }
  199.  
  200. function saveFile(url, name, onError) {
  201. var save = function(url) {
  202. var a = ce('a');
  203. a.href = url;
  204. a.download = name;
  205. a.dispatchEvent(new MouseEvent('click'));
  206. };
  207. if(contains(['blob:', 'data:'], url.substr(0, 5))) return save(url);
  208. GM_xmlhttpRequest({
  209. method: 'GET',
  210. url: url,
  211. responseType: 'blob',
  212. onload: function(res) {
  213. try {
  214. var ou = wn.URL.createObjectURL(res.response);
  215. save(ou);
  216. wn.setTimeout(function() { wn.URL.revokeObjectURL(ou); }, 1000);
  217. } catch(ex) {
  218. onError(ex);
  219. }
  220. },
  221. onError: onError
  222. });
  223. }
  224.  
  225. function onContext(e) {
  226. if(e.shiftKey) return;
  227. if(cfg.zoom == 'context' && _.popup && toggleZoom()) return drop(e);
  228. if((cfg.start == 'context' || (cfg.start == 'auto' && _.manual)) && !_.status && !_.popup) {
  229. startPopup();
  230. return drop(e);
  231. }
  232. wn.setTimeout(function() { deactivate(true); }, 50);
  233. }
  234.  
  235. function onMessage(e) {
  236. if(!contains(trusted, e.origin.substr(e.origin.indexOf('//') + 2)) || typeof e.data != 'string' || e.data.indexOf('mpiv-rule ') !== 0) return;
  237. if(!qs('#mpiv-setup', d)) setup();
  238. var inp = qs('#mpiv-hosts input:first-of-type', d);
  239. inp.value = e.data.substr(10).trim();
  240. inp.dispatchEvent(new Event('input', {bubbles:true}));
  241. inp.parentNode.scrollTop = 0;
  242. inp.select();
  243. }
  244.  
  245. function startPopup() {
  246. setStatus(false);
  247. _.g ? startGalleryPopup() : startSinglePopup(_.url);
  248. }
  249.  
  250. function startSinglePopup(url) {
  251. setStatus('loading');
  252. delete _.iurl;
  253. if(_.follow && !_.q && !_.s) {
  254. return findRedirect(_.url, function(url) {
  255. var info = findInfo(url, _.node, true);
  256. if(!info || !info.url) throw "Couldn't follow redirection target: " + url;
  257. restartSinglePopup(info);
  258. });
  259. }
  260. if(!_.q || Array.isArray(_.urls)) {
  261. if(typeof _.c == 'function') {
  262. _.caption = _.c(d.documentElement.outerHTML, d, _.node);
  263. } else if(typeof _.c == 'string') {
  264. var cnode = findNode(_.c, d);
  265. _.caption = cnode ? findCaption(cnode) : '';
  266. }
  267. url = url.replace(/\/[^\/]*$/, '/700-nw.jpg')
  268. _.iurl = url;
  269. return _.xhr ? downloadImage(url, _.url) : setPopup(url);
  270. }
  271. parsePage(url, function(iurl, cap, url) {
  272. if(!iurl) throw 'File not found.';
  273. if(typeof cap != 'undefined') _.caption = cap;
  274. if(_.follow === true || typeof _.follow == 'function' && _.follow(iurl)) {
  275. var info = findInfo(iurl, _.node, true);
  276. if(!info || !info.url) throw "Couldn't follow URL: " + iurl;
  277. return restartSinglePopup(info);
  278. }
  279. _.iurl = iurl;
  280. if(_.xhr) downloadImage(iurl, url); else setPopup(iurl);
  281. });
  282. }
  283.  
  284. function restartSinglePopup(info) {
  285. for(var prop in info) _[prop] = info[prop];
  286. startSinglePopup(_.url);
  287. }
  288.  
  289. function startGalleryPopup() {
  290. setStatus('loading');
  291. var startUrl = _.url;
  292. downloadPage(_.url, function(text, url) {
  293. try {
  294. var cb = function(items) {
  295. if(!_.url || _.url != startUrl) return;
  296. _.gItems = items;
  297. if(_.gItems.length == 0) {
  298. _.gItems = false;
  299. throw 'empty';
  300. }
  301. _.gIndex = findGalleryPosition(_.url);
  302. wn.setTimeout(nextGalleryItem, 0);
  303. };
  304. var items = _.g(text, url, cb);
  305. if(typeof items != 'undefined') cb(items);
  306. } catch(ex) {
  307. handleError('Parsing error: ' + ex);
  308. }
  309. });
  310. }
  311.  
  312. function findGalleryPosition(gUrl) {
  313. var dir = 0, sel = gUrl.split('#')[1];
  314. if(sel) {
  315. if(/^[0-9]+$/.test(sel)) {
  316. dir += parseInt(sel);
  317. } else {
  318. for(var i = _.gItems.length; i--;) {
  319. var url = _.gItems[i].url;
  320. if(Array.isArray(url)) url = url[0];
  321. var file = url.substr(url.lastIndexOf('/') + 1);
  322. if(contains(file, sel)) {
  323. dir += i;
  324. break;
  325. }
  326. }
  327. }
  328. }
  329. return dir;
  330. }
  331.  
  332. function loadGalleryParser(g) {
  333. if(typeof g == 'function') return g;
  334. if(typeof g == 'string') return new Function('text', 'url', 'cb', g);
  335. return function(text, url) {
  336. var qE = g.entry, qC = g.caption, qI = g.image, qT = g.title, fix = (typeof g.fix == 'string' ? new Function('s', 'isURL', g.fix) : g.fix) || function(s) { return s.trim(); };
  337. var doc = createDoc(text), items = [], nodes = qsa(qE || qI, doc);
  338. if(!Array.isArray(qC)) qC = [qC];
  339. for(var i = 0, node, len = nodes.length; i < len && (node = nodes[i]); i++) {
  340. var item = {};
  341. try {
  342. item.url = fix(findFile(qE ? qs(qI, node) : node, url), true);
  343. item.desc = qC.reduce(function(prev, q) {
  344. var n = qs(q, node);
  345. if(!n) {
  346. [node.previousElementSibling, node.nextElementSibling].forEach(function(es) {
  347. if(es && matches(es, qE) === false) n = matches(es, q) ? es : qs(q, es);
  348. });
  349. }
  350. return n ? (prev ? prev + ' - ' : '') + fix(n.textContent) : prev;
  351. }, '');
  352. } catch(ex) {}
  353. if(item.url) items.push(item);
  354. }
  355. var title = qs(qT, doc);
  356. if(title) items.title = fix(title.getAttribute('content') || title.textContent);
  357. return items;
  358. };
  359. }
  360.  
  361. function nextGalleryItem(dir) {
  362. if(dir > 0 && (_.gIndex += dir) >= _.gItems.length)
  363. _.gIndex = 0;
  364. else if(dir < 0 && (_.gIndex += dir) < 0)
  365. _.gIndex = _.gItems.length - 1;
  366. var item = _.gItems[_.gIndex];
  367. if(Array.isArray(item.url)) {
  368. _.urls = item.url.slice(0);
  369. _.url = _.urls.shift();
  370. } else {
  371. delete _.urls;
  372. _.url = item.url;
  373. }
  374. setPopup(false);
  375. startSinglePopup(_.url);
  376. showFileInfo();
  377. preloadNextGalleryItem(dir);
  378. }
  379.  
  380. function preloadNextGalleryItem(dir) {
  381. var idx = _.gIndex + dir;
  382. if(_.popup && idx >= 0 && idx < _.gItems.length) {
  383. var url = _.gItems[idx].url;
  384. if(Array.isArray(url)) url = url[0];
  385. on(_.popup, 'load', function() {
  386. var img = ce('img');
  387. img.src = url;
  388. });
  389. }
  390. }
  391.  
  392. function activate(node, force) {
  393. if(node == _.popup || node == d.body || node == d.documentElement) return;
  394. var info = parseNode(node);
  395. if(!info || !info.url || info.node == _.node) return;
  396. if(info.distinct && !force) {
  397. var scale = findScale(info.url, info.node.parentNode);
  398. if(scale && scale < cfg.scale) return;
  399. }
  400. if(_.node) deactivate();
  401. _ = info;
  402. _.view = viewRect();
  403. if(cfg.css || _.css) _.style = addStyle((contains(cfg.css, '{') ? cfg.css : '#mpiv-popup {' + cfg.css + '}') + (_.css ? _.css : ''));
  404. _.zooming = contains(cfg.css, 'mpiv-zooming');
  405. [_.node.parentNode, _.node, _.node.firstElementChild].some(function(n) {
  406. if(n && n.title && n.title != n.textContent && !contains(d.title, n.title) && !/^http\S+$/.test(n.title)) {
  407. _.tooltip = {node:n, text:n.title};
  408. n.title = '';
  409. return true;
  410. }
  411. });
  412. on(d, 'mousemove', onMouseMove);
  413. on(d, 'mouseout', onMouseOut);
  414. on(d, 'mousedown', onMouseDown);
  415. on(d, 'contextmenu', onContext);
  416. on(d, 'keydown', onKeyDown);
  417. on(d, 'keyup', onKeyUp);
  418. on(d, 'onwheel' in d ? 'wheel' : 'mousewheel', onMouseScroll);
  419. return true;
  420. }
  421.  
  422. function deactivate(wait) {
  423. wn.clearTimeout(_.timeout);
  424. if(_.req) try { _.req.abort(); } catch(ex) {}
  425. if(_.tooltip) _.tooltip.node.title = _.tooltip.text;
  426. updateTitle(true);
  427. setStatus(false);
  428. setPopup(false);
  429. setBar(false);
  430. rm(_.style);
  431. _ = {};
  432. off(d, 'mousemove', onMouseMove);
  433. off(d, 'mouseout', onMouseOut);
  434. off(d, 'mousedown', onMouseDown);
  435. off(d, 'contextmenu', onContext);
  436. off(d, 'keydown', onKeyDown);
  437. off(d, 'keyup', onKeyUp);
  438. off(d, 'onwheel' in d ? 'wheel' : 'mousewheel', onMouseScroll);
  439. if(wait) {
  440. enabled = false;
  441. wn.setTimeout(function() { enabled = true; }, 200);
  442. }
  443. }
  444.  
  445. function parseNode(node) {
  446. var a, img, url, info;
  447. if(!hosts) { hosts = loadHosts(); GM_registerMenuCommand('Set up Mouseover Popup Image Viewer', setup); }
  448. if(tag(node) == 'A') {
  449. a = node;
  450. } else {
  451. if(tag(node) == 'IMG') {
  452. img = node;
  453. if(img.src.substr(0, 5) != 'data:') url = rel2abs(img.src, location.href);
  454. }
  455. info = findInfo(url, node);
  456. if(info) return info;
  457. a = tag(node.parentNode) == 'A' ? node.parentNode : (tag(node.parentNode.parentNode) == 'A' ? node.parentNode.parentNode : false);
  458. }
  459. if(a) {
  460. url = a.getAttribute('data-expanded-url') || a.getAttribute('data-full-url') || a.getAttribute('data-url') || a.href;
  461. if(url.length > 750 || url.substr(0, 5) == 'data:') url = false;
  462. else if(contains(url, '//t.co/')) url = 'http://' + a.textContent;
  463. info = findInfo(url, a);
  464. if(info) return info;
  465. }
  466. if(img) return {url:img.src, node:img, rect:rect(img), distinct:true};
  467. }
  468.  
  469. function findInfo(url, node, noHtml, skipHost) {
  470. for(var i = 0, len = hosts.length, tn = tag(node), h, m, html, urls; i < len && (h = hosts[i]); i++) {
  471. if(h.e && !matches(node, h.e) || h == skipHost) continue;
  472. if(h.r) {
  473. if(h.html && !noHtml && (tn == 'A' || tn == 'IMG' || h.e)) {
  474. if(!html) html = node.outerHTML;
  475. m = h.r.exec(html);
  476. } else if(url) {
  477. m = h.r.exec(url);
  478. } else {
  479. m = null;
  480. }
  481. } else {
  482. m = url ? /.*/.exec(url) : [];
  483. }
  484. if(!m || tn == 'IMG' && !('s' in h)) continue;
  485. if('s' in h) {
  486. urls = (Array.isArray(h.s) ? h.s : [h.s]).map(function(s) { if(typeof s == 'string') return decodeURIComponent(replace(s, m)); if(typeof s == 'function') return s(m, node); return s; });
  487. if(h.q && urls.length > 1) { console.log('Rule discarded. Substitution arrays can\'t be combined with property q.'); continue; }
  488. if(Array.isArray(urls[0])) urls = urls[0];
  489. if(urls[0] === false) continue;
  490. urls = urls.map(function(u) { return u ? decodeURIComponent(u) : u; });
  491. } else {
  492. urls = [m.input];
  493. }
  494. if((h.follow === true || typeof h.follow == 'function' && h.follow(urls[0])) && !h.q && h.s) return findInfo(urls[0], node, false, h);
  495. var info = {
  496. node: node,
  497. url: urls.shift(),
  498. urls: urls.length ? urls : false,
  499. r: h.r,
  500. q: h.q,
  501. c: h.c,
  502. g: h.g ? loadGalleryParser(h.g) : h.g,
  503. xhr: cfg.xhr && h.xhr,
  504. tabfix: h.tabfix,
  505. post: typeof h.post == 'function' ? h.post(m) : h.post,
  506. follow: h.follow,
  507. css: h.css,
  508. manual: h.manual,
  509. distinct: h.distinct,
  510. rect: rect(node, h.rect)
  511. };
  512. if(contains(hostname, 'twitter.com') && !/(facebook|google|twimg|twitter)\.com\//.test(info.url) || hostname == 'github.com' && !/github/.test(info.url) || contains(hostname, 'facebook.com') && /\bimgur\.com/.test(info.url)) info.xhr = 'data';
  513. return info;
  514. }
  515. }
  516.  
  517. function downloadPage(url, cb) {
  518. var req, opts = {
  519. method: 'GET',
  520. url: url,
  521. onload: function(res) {
  522. try {
  523. if(req != _.req) return;
  524. delete _.req;
  525. if(res.status > 399) throw 'Server error: ' + res.status;
  526. cb(res.responseText, res.finalUrl || url);
  527. } catch(ex) {
  528. handleError(ex);
  529. }
  530. },
  531. onerror: function(res) {
  532. if(req == _.req) handleError(res);
  533. }
  534. };
  535. if(_.post) {
  536. opts.method = 'POST';
  537. opts.data = _.post;
  538. opts.headers = {'Content-Type':'application/x-www-form-urlencoded','Referer':url};
  539. }
  540. _.req = req = GM_xmlhttpRequest(opts);
  541. }
  542.  
  543. function downloadImage(url, referer) {
  544. var start = Date.now(), bar, req;
  545. _.req = req = GM_xmlhttpRequest({
  546. method: 'GET',
  547. url: url,
  548. overrideMimeType: 'text/plain; charset=x-user-defined',
  549. headers: {'Accept':'image/png,image/*;q=0.8,*/*;q=0.5','Referer':referer},
  550. onprogress: function(e) {
  551. if(req != _.req) return;
  552. if(!bar && Date.now() - start > 3000 && e.loaded/e.total < 0.5) bar = true;
  553. if(bar) setBar(parseInt(e.loaded/e.total * 100) + '% of ' + (e.total/1000000).toFixed(1) + ' MB', 'xhr');
  554. },
  555. onload: function(res) {
  556. try {
  557. if(req != _.req) return;
  558. delete _.req;
  559. setBar(false);
  560. if(res.status > 399) throw 'HTTP error ' + res.status;
  561. var txt = res.responseText, ui8 = new Uint8Array(txt.length), type;
  562. for(var i = txt.length; i--;) {
  563. ui8[i] = txt.charCodeAt(i);
  564. }
  565. if(/Content-Type:\s*(.+)/i.exec(res.responseHeaders) && !contains(RegExp.$1, 'text/plain')) type = RegExp.$1;
  566. if(!type) {
  567. var ext = /\.([a-z0-9]+?)($|\?|#)/i.exec(url) ? RegExp.$1.toLowerCase() : 'jpg', types = {bmp:'image/bmp', gif:'image/gif', jpe:'image/jpeg', jpeg:'image/jpeg', jpg:'image/jpeg', mp4:'video/mp4', png:'image/png', svg:'image/svg+xml', tif:'image/tiff', tiff:'image/tiff', webm:'video/webm'};
  568. type = ext in types ? types[ext] : 'application/octet-stream';
  569. }
  570. var b = new Blob([ui8.buffer], {type:type});
  571. if(wn.URL && _.xhr != 'data') return setPopup(wn.URL.createObjectURL(b));
  572. var fr = new FileReader();
  573. fr.onload = function() { setPopup(fr.result); };
  574. fr.onerror = handleError;
  575. fr.readAsDataURL(b);
  576. } catch(ex) {
  577. handleError(ex);
  578. }
  579. },
  580. onerror: function(res) {
  581. if(req == _.req) handleError(res);
  582. }
  583. });
  584. }
  585.  
  586. function findRedirect(url, cb) {
  587. var req;
  588. _.req = req = GM_xmlhttpRequest({
  589. url: url,
  590. method: 'HEAD',
  591. headers: {Referer:location.href.replace(location.hash, '')},
  592. onload: function(res) {
  593. if(req == _.req) cb(res.finalUrl);
  594. }
  595. });
  596. }
  597.  
  598. function parsePage(url, cb) {
  599. downloadPage(url, function(html, url) {
  600. var iurl, cap, doc = createDoc(html);
  601. if(typeof _.q == 'function') {
  602. iurl = _.q(html, doc, _.node);
  603. if(Array.isArray(iurl)) {
  604. _.urls = iurl.slice(0);
  605. iurl = _.urls.shift();
  606. }
  607. } else {
  608. var inode = findNode(_.q, doc);
  609. iurl = inode ? findFile(inode, url) : false;
  610. }
  611. if(typeof _.c == 'function') {
  612. cap = _.c(html, doc, _.node);
  613. } else if(typeof _.c == 'string') {
  614. var cnode = findNode(_.c, doc);
  615. cap = cnode ? findCaption(cnode) : '';
  616. }
  617. cb(iurl, cap, url);
  618. });
  619. }
  620.  
  621. function findNode(q, doc) {
  622. var node;
  623. if(!q) return;
  624. if(!Array.isArray(q)) q = [q];
  625. for(var i = 0, len = q.length; i < len; i++) {
  626. node = qs(q[i], doc);
  627. if(node) break;
  628. }
  629. return node;
  630. }
  631.  
  632. function findFile(n, url) {
  633. var base = qs('base[href]', n.ownerDocument);
  634. var path = n.getAttribute('src') || n.getAttribute('data-m4v') || n.getAttribute('href') || n.getAttribute('content') || /https?:\/\/[.\/a-z0-9_+%\-]+\.(jpe?g|gif|png|svg|webm|mp4)/i.exec(n.outerHTML) && RegExp.lastMatch;
  635. return path ? rel2abs(path.trim(), base ? base.getAttribute('href') : url) : false;
  636. }
  637.  
  638. function findCaption(n) {
  639. return n.getAttribute('content') || n.getAttribute('title') || n.textContent;
  640. }
  641.  
  642. function checkProgress(start) {
  643. if(start === true) {
  644. if(checkProgress.interval) wn.clearInterval(checkProgress.interval);
  645. checkProgress.interval = wn.setInterval(checkProgress, 150);
  646. return;
  647. }
  648. var p = _.popup;
  649. if(!p) return wn.clearInterval(checkProgress.interval);
  650. if(!updateSize()) return;
  651. wn.clearInterval(checkProgress.interval);
  652. if(_.preloadStart) {
  653. var wait = _.preloadStart + cfg.delay - Date.now();
  654. if(wait > 0) return _.timeout = wn.setTimeout(checkProgress, wait);
  655. }
  656. if(_.urls && _.urls.length && Math.max(_.nheight, _.nwidth) < 130) return handleError({type:'error'});
  657. setStatus(false);
  658. p.clientHeight;
  659. p.className = 'mpiv-show';
  660. updateSpacing();
  661. updateScales();
  662. updateTitle();
  663. placePopup();
  664. if(!_.bar) showFileInfo();
  665. if(_.large = _.nwidth > p.clientWidth + _.mbw || _.nheight > p.clientHeight + _.mbh) setStatus('large');
  666. if(cfg.imgtab && imgtab || cfg.zoom == 'auto') toggleZoom();
  667. }
  668.  
  669. function updateSize() {
  670. var p = _.popup;
  671. _.nheight = p.naturalHeight || p.videoHeight || p.loaded && 800;
  672. _.nwidth = p.naturalWidth || p.videoWidth || p.loaded && 1200;
  673. return !!_.nheight;
  674. }
  675.  
  676. function updateSpacing() {
  677. var s = wn.getComputedStyle(_.popup);
  678. _.pw = styleSum(s, ['padding-left', 'padding-right']);
  679. _.ph = styleSum(s, ['padding-top', 'padding-bottom']);
  680. _.mbw = styleSum(s, ['margin-left', 'margin-right', 'border-left-width', 'border-right-width']);
  681. _.mbh = styleSum(s, ['margin-top', 'margin-bottom', 'border-top-width', 'border-bottom-width']);
  682. }
  683.  
  684. function updateScales() {
  685. var scales = cfg.scales.length ? cfg.scales : ['0!', 0.125, 0.25, 0.5, 0.75, 1, 1.5, 2, 3, 5, 8, 16], fit = Math.min(( _.view.width - _.mbw)/_.nwidth, (_.view.height - _.mbh)/_.nheight), cutoff = _.scale = Math.min(1, fit);
  686. _.scales = [];
  687. for(var i = scales.length; i--;) {
  688. var val = parseFloat(scales[i]) || fit, opt = typeof scales[i] == 'string' ? scales[i].slice(-1) : 0;
  689. if(opt == '!') cutoff = val;
  690. if(opt == '*') _.zscale = val;
  691. if(val != _.scale) _.scales.push(val);
  692. }
  693. _.scales = _.scales.filter(function(x) { return x >= cutoff; });
  694. _.scales.sort(function(a, b) { return a - b; });
  695. _.scales.unshift(_.scale);
  696. }
  697.  
  698. function updateMouse(e) {
  699. _.cx = e.clientX;
  700. _.cy = e.clientY;
  701. var r = _.rect;
  702. if(r) _.cr = _.cx < r.right + 2 && _.cx > r.left - 2 && _.cy < r.bottom + 2 && _.cy > r.top - 2;
  703. }
  704.  
  705. function showFileInfo() {
  706. if(_.gItems) {
  707. var item = _.gItems[_.gIndex];
  708. var c = _.gItems.length > 1 ? '[' + (_.gIndex + 1) + '/' + _.gItems.length + '] ' : '';
  709. if(_.gIndex == 0 && _.gItems.title && (!item.desc || !contains(item.desc, _.gItems.title))) c += _.gItems.title + (item.desc ? ' - ' : '');
  710. if(item.desc) c += item.desc;
  711. if(c) setBar(c.trim(), 'gallery', true);
  712. } else if('caption' in _) {
  713. setBar(_.caption, 'caption');
  714. } else if(_.tooltip) {
  715. setBar(_.tooltip.text, 'tooltip');
  716. }
  717. }
  718.  
  719. function updateTitle(reset) {
  720. if(reset) {
  721. if(typeof _.title == 'string') d.title = _.title;
  722. } else {
  723. if(typeof _.title != 'string') _.title = d.title;
  724. d.title = Math.round(_.scale * 100) + '% - ' + _.nwidth + 'x' + _.nheight;
  725. }
  726. }
  727.  
  728. function placePopup() {
  729. var p = _.popup;
  730. if(!p) return;
  731. var x = null, y = null, w = Math.round(_.scale * _.nwidth), h = Math.round(_.scale * _.nheight), cx = _.cx, cy = _.cy, vw = _.view.width, vh = _.view.height;
  732. if(!_.zoom && (!_.gItems || _.gItems.length < 2) && !cfg.center) {
  733. var r = _.rect, rx = (r.left + r.right) / 2, ry = (r.top + r.bottom) / 2;
  734. if(vw - r.right - 40 > w + _.mbw || w + _.mbw < r.left - 40) {
  735. if(h + _.mbh < vh - 60) y = Math.min(Math.max(ry - h/2, 30), vh - h - 30);
  736. x = rx > vw/2 ? r.left - 40 - w : r.right + 40;
  737. } else if(vh - r.bottom - 40 > h + _.mbh || h + _.mbh < r.top - 40) {
  738. if(w + _.mbw < vw - 60) x = Math.min(Math.max(rx - w/2, 30), vw - w - 30);
  739. y = ry > vh/2 ? r.top - 40 - h : r.bottom + 40;
  740. }
  741. }
  742. if(x == null) x = Math.round((vw > w ? vw/2 - w/2 : -1 * Math.min(1, Math.max(0, 5/3*(cx/vw-0.2))) * (w - vw)) - (_.pw + _.mbw)/2);
  743. if(y == null) y = Math.round((vh > h ? vh/2 - h/2 : -1 * Math.min(1, Math.max(0, 5/3*(cy/vh-0.2))) * (h - vh)) - (_.ph + _.mbh)/2);
  744. p.style.cssText = 'width:' + w + 'px!important;height:' + h + 'px!important;left:' + x + 'px!important;top:' + y + 'px!important';
  745. }
  746.  
  747. function toggleZoom() {
  748. var p = _.popup;
  749. if(!p || !_.scales || _.scales.length < 2) return;
  750. _.zoom = !_.zoom;
  751. _.zoomed = true;
  752. _.scale = _.scales[ _.zoom ? (_.scales.indexOf(_.zscale) > 0 ? _.scales.indexOf(_.zscale) : 1) : 0];
  753. if(_.zooming) p.classList.add('mpiv-zooming');
  754. placePopup();
  755. updateTitle();
  756. setStatus(_.zoom ? 'zoom' : false);
  757. if(cfg.zoom != 'auto') setBar(false);
  758. if(!_.zoom) showFileInfo();
  759. return _.zoom;
  760. }
  761.  
  762. function handleError(o) {
  763. var m = [o.message || (o.readyState ? 'Request failed.' : (o.type == 'error' ? 'File can\'t be displayed.' + (qs('div[bgactive*="flashblock"]', d) ? ' Check Flashblock settings.' : '') : o))];
  764. try {
  765. if(o.stack) m.push(' @ ' + o.stack.replace(/<?@file:.+?\.js/g, ''));
  766. if(_.r) m.push('RegExp: ' + _.r);
  767. if(_.url) m.push('URL: ' + _.url);
  768. if(_.iurl) m.push('File: ' + _.iurl);
  769. console.log(m.join('\n'));
  770. } catch(ex) {}
  771. if(contains(hostname, 'google') && contains(location.search, 'tbm=isch') && !_.xhr && cfg.xhr) {
  772. _.xhr = true;
  773. startSinglePopup(_.url);
  774. } else if(_.urls && _.urls.length) {
  775. _.url = _.urls.shift();
  776. if(!_.url)
  777. deactivate();
  778. else
  779. startSinglePopup(_.url);
  780. } else if(_.node) {
  781. setStatus('error');
  782. setBar(m[0], 'error');
  783. }
  784. }
  785.  
  786. function setStatus(status, flag) {
  787. var de = d.documentElement, cn = de.className;
  788. if(flag == 'remove') {
  789. cn = cn.replace('mpiv-' + status, '');
  790. } else {
  791. if(flag != 'add') cn = cn.replace(/mpiv-[a-z]+/g, '');
  792. if(status && !contains(cn, 'mpiv-' + status)) cn += ' mpiv-' + status;
  793. }
  794. de.className = cn;
  795. }
  796.  
  797. function setPopup(src) {
  798. var p = _.popup;
  799. if(p) {
  800. _.zoom = false;
  801. off(p, 'error', handleError);
  802. if(typeof p.pause == 'function') p.pause();
  803. if(!_.lazyUnload) {
  804. if(p.src.substr(0, 5) == 'blob:') wn.URL.revokeObjectURL(p.src);
  805. p.src = '';
  806. }
  807. rm(p);
  808. delete _.popup;
  809. }
  810. if(!src) return;
  811. if(src.substr(0, 5) != 'data:' && /\.(webm|mp4)($|\?)/.test(src) || src.substr(0, 10) == 'data:video') {
  812. var start = Date.now(), bar;
  813. var onProgress = function(e) {
  814. var p = e.target;
  815. if(!p.duration || !p.buffered.length || Date.now() - start < 2000) return;
  816. var per = parseInt(p.buffered.end(0)/p.duration * 100);
  817. if(!bar && per > 0 && per < 50) bar = true;
  818. if(bar) setBar(per + '% of ' + Math.round(p.duration) + 's', 'xhr');
  819. };
  820. p = _.popup = ce('video');
  821. p.autoplay = true;
  822. p.loop = true;
  823. p.volume = 0.5;
  824. p.controls = false;
  825. on(p, 'progress', onProgress);
  826. on(p, 'canplaythrough', function(e) { off(e.target, 'progress', onProgress); if(_.bar && _.bar.classList.contains('mpiv-xhr')) { setBar(false); showFileInfo(); } });
  827. } else {
  828. p = _.popup = ce('img');
  829. }
  830. p.id = 'mpiv-popup';
  831. on(p, 'error', handleError);
  832. on(p, 'load', function() { this.loaded = true; });
  833. if(_.zooming) on(p, 'transitionend', function(e) { e.target.classList.remove('mpiv-zooming'); });
  834. _.bar ? d.body.insertBefore(p, _.bar) : d.body.appendChild(p);
  835. p.src = src;
  836. p = null;
  837. checkProgress(true);
  838. }
  839.  
  840. function setBar(label, cn) {
  841. var b = _.bar;
  842. if(!label) {
  843. rm(b);
  844. delete _.bar;
  845. return;
  846. }
  847. if(!b) {
  848. b = _.bar = ce('div');
  849. b.id = 'mpiv-bar';
  850. }
  851. b.innerHTML = label;
  852. if(!b.parentNode) {
  853. d.body.appendChild(b);
  854. b.clientHeight;
  855. }
  856. b.className = 'mpiv-show mpiv-' + cn;
  857. }
  858.  
  859. function rel2abs(rel, abs) {
  860. if(rel.substr(0, 5) == 'data:') return rel;
  861. var re = /^([a-z]+:)\/\//;
  862. if(re.test(rel)) return rel;
  863. if(!re.exec(abs)) return;
  864. if(rel.indexOf('//') === 0) return RegExp.$1 + rel;
  865. if(rel[0] == '/') return abs.substr(0, abs.indexOf('/', RegExp.lastMatch.length)) + rel;
  866. return abs.substr(0, abs.lastIndexOf('/')) + '/' + rel;
  867. }
  868.  
  869. function replace(s, m) {
  870. if(!m) return s;
  871. if(s.charAt(0) == '/' && s.charAt(1) != '/') {
  872. var mid = /[^\\]\//.exec(s).index+1;
  873. var end = s.lastIndexOf('/');
  874. var re = new RegExp(s.substring(1, mid), s.substr(end+1));
  875. return m.input.replace(re, s.substring(mid+1, end));
  876. }
  877. for(var i = m.length; i--;) {
  878. s = s.replace('$'+i, m[i]);
  879. }
  880. return s;
  881. }
  882.  
  883. function addStyle(css) {
  884. var s = ce('style');
  885. s.textContent = css;
  886. d.head.appendChild(s);
  887. return s;
  888. }
  889.  
  890. function styleSum(s, p) {
  891. for(var i = p.length, x = 0; i--;) {
  892. x += parseInt(s.getPropertyValue([p[i]])) || 0;
  893. }
  894. return x;
  895. }
  896.  
  897. function findScale(url, parent) {
  898. var imgs = qsa('img, video', parent);
  899. for(var i = imgs.length, img; i-- && (img = imgs[i]);) {
  900. if(img.src != url) continue;
  901. var s = Math.max((img.naturalHeight || img.videoHeight)/img.offsetHeight, (img.naturalWidth || img.videoWidth)/img.offsetWidth);
  902. if(isFinite(s)) return s;
  903. }
  904. }
  905.  
  906. function viewRect() {
  907. var node = d.compatMode == 'BackCompat' ? d.body : d.documentElement;
  908. return {width:node.clientWidth, height:node.clientHeight};
  909. }
  910.  
  911. function rect(node, q) {
  912. var n;
  913. if(q) {
  914. n = node;
  915. while(tag(n = n.parentNode) != 'BODY') {
  916. if(matches(n, q)) return n.getBoundingClientRect();
  917. }
  918. }
  919. var nodes = qsa('*', node);
  920. for(var i = nodes.length; i-- && (n = nodes[i]);) {
  921. if(n.offsetHeight > node.offsetHeight) node = n;
  922. }
  923. return node.getBoundingClientRect();
  924. }
  925.  
  926. function matches(n, q) {
  927. var p = Element.prototype, m = p.matches || p.mozMatchesSelector || p.webkitMatchesSelector || p.oMatchesSelector;
  928. if(m) return m.call(n, q);
  929. }
  930.  
  931. function closest(n, q) {
  932. while(n) {
  933. if(matches(n, q)) return n;
  934. n = n.parentNode;
  935. }
  936. }
  937.  
  938. function tag(n) {
  939. return n.tagName.toUpperCase();
  940. }
  941.  
  942. function createDoc(text) {
  943. var doc = d.implementation.createHTMLDocument('MPIV');
  944. doc.documentElement.innerHTML = text;
  945. return doc;
  946. }
  947.  
  948. function rm(n) {
  949. if(n && n.parentNode) n.parentNode.removeChild(n);
  950. }
  951.  
  952. function on(n, e, f) {
  953. n.addEventListener(e, f);
  954. }
  955.  
  956. function off(n, e, f) {
  957. n.removeEventListener(e, f);
  958. }
  959.  
  960. function drop(e) {
  961. e.preventDefault();
  962. e.stopPropagation();
  963. }
  964.  
  965. function ce(s) {
  966. return d.createElement(s);
  967. }
  968.  
  969. function qs(s, n) {
  970. return n.querySelector(s);
  971. }
  972.  
  973. function qsa(s, n) {
  974. return n.querySelectorAll(s);
  975. }
  976.  
  977. function contains(a, b) {
  978. return a && a.indexOf(b) > -1;
  979. }
  980.  
  981. function setup() {
  982. var $ = function(s) { return d.getElementById('mpiv-'+s); };
  983. var close = function() { rm($('setup')); if(!contains(trusted, hostname)) off(wn, 'message', onMessage); };
  984. var update = function() { $('delay').parentNode.style.display = $('preload').parentNode.style.display = $('start-auto').selected ? '' : 'none'; };
  985. var check = function(e) {
  986. var t = e.target, ok;
  987. try {
  988. var pes = t.previousElementSibling;
  989. if(t.value) {
  990. if(!pes) { var inp = t.cloneNode(); inp.value = ''; t.parentNode.insertBefore(inp, t); }
  991. new RegExp(JSON.parse(t.value).r);
  992. } else if(pes) {
  993. pes.focus();
  994. rm(t);
  995. }
  996. ok = 1;
  997. } catch(ex) {}
  998. t.style.backgroundColor = ok ? '' : '#ffaaaa';
  999. };
  1000. var exp = function(e) {
  1001. drop(e);
  1002. var s = JSON.stringify(getCfg());
  1003. if(typeof GM_setClipboard == 'function') {
  1004. GM_setClipboard(s);
  1005. wn.alert('Settings copied to clipboard!');
  1006. } else {
  1007. wn.alert(s);
  1008. }
  1009. };
  1010. var imp = function(e) {
  1011. drop(e);
  1012. var s = wn.prompt('Paste settings:');
  1013. if(!s) return;
  1014. init(fixCfg(s));
  1015. };
  1016. var install = function(e) {
  1017. drop(e);
  1018. e.target.parentNode.innerHTML = '<span>Loading...</span><iframe style="width:100%;height:26px;border:0;margin:0;display:none;" src="//w9p.co/userscripts/mpiv/more_host_rules.html" onload="this.style.display=\'\';this.previousElementSibling.style.display=\'none\';"></iframe>';
  1019. };
  1020. var getCfg = function() {
  1021. var cfg = {};
  1022. var delay = parseInt($('delay').value);
  1023. if(!isNaN(delay) && delay >= 0) cfg.delay = delay;
  1024. var scale = parseFloat($('scale').value.replace(',', '.'));
  1025. if(!isNaN(scale)) cfg.scale = Math.max(1, scale);
  1026. cfg.start = $('start-context').selected ? 'context' : ($('start-ctrl').selected ? 'ctrl' : 'auto');
  1027. cfg.zoom = $('zoom-context').selected ? 'context' : ($('zoom-wheel').selected ? 'wheel' : ($('zoom-shift').selected ? 'shift' : 'auto'));
  1028. cfg.center = $('center').checked;
  1029. cfg.imgtab = $('imgtab').checked;
  1030. cfg.close = $('close').selected;
  1031. cfg.preload = $('preload').checked;
  1032. cfg.css = $('css').value.trim();
  1033. cfg.scales = $('scales').value.trim().split(/[,;]*\s+/).map(function(x) { return x.replace(',', '.'); }).filter(function(x) { return !isNaN(parseFloat(x)); });
  1034. cfg.xhr = $('xhr').checked;
  1035. var inps = qsa('input', $('hosts')), lines = [];
  1036. for(var i = 0; i < inps.length; i++) {
  1037. var s = inps[i].value.trim();
  1038. if(s) lines.push(s);
  1039. }
  1040. lines.sort();
  1041. cfg.hosts = lines.join('\n');
  1042. return fixCfg(JSON.stringify(cfg));
  1043. };
  1044. var init = function(cfg) {
  1045. close();
  1046. if(!contains(trusted, hostname)) on(wn, 'message', onMessage);
  1047. addStyle('\
  1048. #mpiv-setup { position:fixed;z-index:2147483647;top:20px;right:20px;padding:20px 30px;background:#eee;width:640px;border:1px solid black; }\
  1049. #mpiv-setup * { color:black;text-align:left;min-height:unset;margin:unset;padding:unset;line-height:15px;font-size:12px;font-family:sans-serif;box-shadow:none; }\
  1050. #mpiv-setup a { color:darkblue!important;text-decoration:underline!important; }\
  1051. #mpiv-setup ul { margin:10px 0 15px 0;padding:0;list-style:none;background:#eee;border:0; }\
  1052. #mpiv-setup input, #mpiv-setup select, #mpiv-css { display:inline;border:1px solid gray;padding:2px;background:white; }\
  1053. #mpiv-css { resize:vertical; height:45px; }\
  1054. #mpiv-scales { width:130px; }\
  1055. #mpiv-setup li { margin:0;padding:7px 0;vertical-align:middle;background:#eee;border:0 }\
  1056. #mpiv-zoom { margin-right: 18px; }\
  1057. #mpiv-delay, #mpiv-scale { width:36px; }\
  1058. #mpiv-cursor, #mpiv-imgtab, #mpiv-xhr, #mpiv-preload { margin-left:18px; }\
  1059. #mpiv-hosts { max-height:150px;overflow-y:auto; padding:2px; margin:4px 0;clear:both; }\
  1060. #mpiv-hosts input, #mpiv-css { width:98%;margin:3px 0; }\
  1061. #mpiv-setup button { width:150px;margin:0 10px;text-align:center; }\
  1062. ');
  1063. var div = ce('div');
  1064. div.id = 'mpiv-setup';
  1065. d.body.appendChild(div);
  1066. div.innerHTML = '\
  1067. <div><a href="http://w9p.co/userscripts/mpiv/">Sima-land.ru Popup Original Image Viewer/Downloader</a><span style="float:right"><a href="#" id="mpiv-import">Import</a> | <a href="#" id="mpiv-export">Export</a></span></div><ul>\
  1068. <li>Popup: <select><option id="mpiv-start-auto">automatically</option><option id="mpiv-start-context">right click or ctrl</option><option id="mpiv-start-ctrl">ctrl</option></select> <span>after <input id="mpiv-delay" type="text"/> ms</span> <span><input type="checkbox" id="mpiv-preload"/> Start loading immediately</span></li>\
  1069. <li>Only show popup over scaled-down image when natural size is <input id="mpiv-scale" type="text"/> times larger</li>\
  1070. <li><input type="checkbox" id="mpiv-center"/> Always centered <input type="checkbox" id="mpiv-imgtab"/> Run in image tabs <input type="checkbox" id="mpiv-xhr" onclick="return this.checked || confirm(\'Do not disable this unless you spoof the HTTP headers yourself.\')"/> Anti-hotlinking workaround</li>\
  1071. <li>Zoom: <select id="mpiv-zoom"><option id="mpiv-zoom-context">right click or shift</option><option id="mpiv-zoom-wheel">wheel up or shift</option><option id="mpiv-zoom-shift">shift</option><option id="mpiv-zoom-auto">automatically</option></select> Custom scale factors: <input type="text" id="mpiv-scales" placeholder="e.g. 0 0.5 1* 2"/> <span title="values smaller than non-zoomed size are ignored, 0 = fit to window, 0! = same as 0 but also removes smaller values, asterisk after value marks default zoom factor (e.g. 1*)" style="cursor:help">(?)</span></li>\
  1072. <li>If zooming out further is not possible, <select><option>stay in zoom mode</option><option id="mpiv-close">close popup</option></select></li>\
  1073. <li><a href="http://w9p.co/userscripts/mpiv/css.html" target="_blank">Custom CSS:</a><div><textarea id="mpiv-css" spellcheck="false"></textarea></li>\
  1074. <li><a href="http://w9p.co/userscripts/mpiv/host_rules.html" target="_blank">Custom host rules:</a><input type="text" id="mpiv-search" placeholder="Search" style="display:none;float:right;width:70px;padding:1px 2px;font-size:10px;"/><div id="mpiv-hosts"><input type="text" spellcheck="false"></div></li>\
  1075. <li><a href="#" id="mpiv-install">Install rule from repository...</a></li>\
  1076. </ul><div style="text-align:center"><button id="mpiv-ok">OK</button><button id="mpiv-cancel">Cancel</button></div>';
  1077. if(cfg.hosts) {
  1078. var parent = $('hosts');
  1079. var lines = cfg.hosts.split(/[\r\n]+/);
  1080. for(var i = 0, s; i < lines.length && (s = lines[i]); i++) {
  1081. var inp = parent.firstElementChild.cloneNode();
  1082. inp.value = s;
  1083. parent.appendChild(inp);
  1084. check({target:inp});
  1085. }
  1086. if(lines.length > 5 || setup.search) {
  1087. var se = $('search'), sf = function() {
  1088. var inps = qsa('input', $('hosts')), s = se.value.toLowerCase();
  1089. setup.search = s;
  1090. for(var i = 0; i < inps.length; i++) {
  1091. inps[i].style.display = !inps[i].value || contains(inps[i].value.toLowerCase(), s) ? '' : 'none';
  1092. }
  1093. };
  1094. on(se, 'input', sf);
  1095. se.value = setup.search || '';
  1096. if(se.value) sf();
  1097. se.style.display = '';
  1098. }
  1099. }
  1100. on($('start-auto').parentNode, 'change', update);
  1101. on($('cancel'), 'click', close);
  1102. on($('export'), 'click', exp);
  1103. on($('import'), 'click', imp);
  1104. on($('hosts'), 'input', check);
  1105. on($('install'), 'click', install);
  1106. on($('ok'), 'click', function() {
  1107. saveCfg(getCfg());
  1108. hosts = loadHosts();
  1109. close();
  1110. });
  1111. $('delay').value = cfg.delay;
  1112. $('scale').value = cfg.scale;
  1113. $('center').checked = cfg.center;
  1114. $('imgtab').checked = cfg.imgtab;
  1115. $('close').selected = cfg.close;
  1116. $('preload').checked = cfg.preload;
  1117. $('css').value = cfg.css;
  1118. $('scales').value = cfg.scales.join(' ');
  1119. $('xhr').checked = cfg.xhr;
  1120. $('zoom-' + cfg.zoom).selected = true;
  1121. $('start-' + cfg.start).selected = true;
  1122. update();
  1123. var free = viewRect().height - div.offsetHeight - 60;
  1124. $('hosts').style.maxHeight = parseInt($('hosts').offsetHeight + 0.8 * free) + 'px';
  1125. $('css').style.height = parseInt($('css').offsetHeight + 0.2 * free) + 'px';
  1126. div = null;
  1127. };
  1128. init(loadCfg());
  1129. }
  1130.  
  1131. addStyle('\
  1132. #mpiv-bar { position:fixed;z-index:2147483647;left:0;right:0;top:0;transform:scaleY(0);-webkit-transform:scaleY(0);transform-origin:top;-webkit-transform-origin:top;transition:transform 500ms ease 1000ms;-webkit-transition:-webkit-transform 500ms ease 1000ms;text-align:center;font-family:sans-serif;font-size:15px;font-weight:bold;background:rgba(0, 0, 0, 0.6);color:white;padding:4px 10px; }\
  1133. #mpiv-bar.mpiv-show { transform:scaleY(1);-webkit-transform:scaleY(1); }\
  1134. #mpiv-popup.mpiv-show { display:inline; }\
  1135. #mpiv-popup { display:none;border:1px solid gray;box-sizing:content-box;background-color:white;position:fixed;z-index:2147483647;margin:0;max-width:none;max-height:none;will-change:display,width,height,left,top;cursor:none; }\
  1136. .mpiv-loading:not(.mpiv-preloading) * { cursor:wait!important; }\
  1137. .mpiv-edge #mpiv-popup { cursor:default; }\
  1138. .mpiv-error * { cursor:not-allowed!important; }\
  1139. .mpiv-ready *, .mpiv-large * { cursor:zoom-in!important; cursor:-webkit-zoom-in!important; }\
  1140. .mpiv-shift * { cursor:default!important; }');
  1141. on(d, 'mouseover', onMouseOver);
  1142. if(contains(hostname, 'google')) {
  1143. var node = d.getElementById('main');
  1144. if(node) on(node, 'mouseover', onMouseOver);
  1145. } else if(contains(trusted, hostname)) {
  1146. on(wn, 'message', onMessage);
  1147. on(d, 'click', function(e) {
  1148. var t = e.target;
  1149. if(e.which != 1 || !/BLOCKQUOTE|CODE|PRE/.test(tag(t) + tag(t.parentNode)) || !/^\s*\{\s*".+:.+\}\s*$/.test(t.textContent)) return;
  1150. wn.postMessage('mpiv-rule ' + t.textContent, '*');
  1151. e.preventDefault();
  1152. });
  1153. }