1/* 2ÐÑи иÑполÑзовании Ñега <select> обзаÑелÑно пÑопиÑÑваÑÑ ÐºÐ»Ð°ÑÑ 3'select-control' и аÑÑибÑÑ 'data-type', имеÑÑий ÑледÑÑÑие знаÑениÑ: 4simple - Ð´Ð»Ñ Ð¾Ð±ÑÑного <select>, напÑимеÑ, вÑÐ±Ð¾Ñ Ñели знакомÑÑва или даÑÑ ÑождениÑ; 5simple-search - Ð´Ð»Ñ Ð¾Ð±ÑÑного <select> Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾ÑÑÑÑ Ð²ÑбоÑа или поиÑка по options; 6geo-search - <select> Ñ Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð¾ÑÑÑÑ Ð²ÑбоÑа или поиÑка наÑелÑнного пÑнкÑа 7*/ 8 9const Controll = (() => { 10 'use strict'; 11 12 const MINREQ = 2; 13 const MINREQTAG = 3; 14 15 class SelectSimple { 16 constructor(s) { 17 this.s = s; 18 this.class = s.className; 19 this.name = s.getAttribute('name'); 20 this.type = s.dataset.type; 21 this.options = s.options; 22 this.parent = s.parentNode; 23 this.searchArray = []; 24 25 s.hidden = true; 26 27 this.createElementsSimple('createInitalize'); 28 this.setHandlers(); 29 } 30 31 createElementsSimple(fname) { 32 let panel, content; 33 34 const fn = { 35 createInitalize: () => { 36 this.container = document.createElement('div'); 37 this.container.className = `${this.class} select-container`; 38 this.container.setAttribute('tabindex', Math.floor(Math.random() * 1000)); 39 if (this.s.hasAttribute('data-targid')) { 40 this.container.id = this.s.id; 41 this.container.dataset.targid = this.s.dataset.targid; 42 } 43 44 this.select = fn.createSelect(); 45 this.dropPanel = fn.createDropPanel('drop-panel'); 46 47 this.container.append(this.select); 48 this.container.append(this.dropPanel); 49 this.s.after(this.container); 50 51 this.scrollbox = new ScrollBox(this.dropPanel); 52 }, 53 54 createSelect: () => { 55 const select = document.createElement('div'); 56 select.className = 'select-input'; 57 58 this.txtnode = document.createElement('div'); 59 this.txtnode.className = 'select-text'; 60 61 this.arrow = document.createElement('i'); 62 this.arrow.className = 'icon-stack down-gray'; 63 64 select.append(this.txtnode); 65 select.append(this.arrow); 66 67 this.setSelectedItemData(); 68 69 return select; 70 }, 71 72 createDropPanel: () => { 73 const [panel, content] = fn.createScrollElements(); 74 this.contentBox = content; 75 76 let list = document.createElement('ul'); 77 list.className = 'reset options-list'; 78 this.ul = this.otputListItemsDrop(list); 79 this.items = this.ul.children; 80 81 content.append(this.ul); 82 panel.append(content); 83 84 return panel; 85 }, 86 87 createScrollElements: () => { 88 panel = document.createElement('div'); 89 panel.className = 'scroll-container drop-panel'; 90 91 content = document.createElement('div'); 92 content.className = 'scroll-content'; 93 94 return [panel, content]; 95 }, 96 }; 97 98 fn[fname](); 99 return [panel, content]; 100 } 101 102 reInitalize() { 103 // замениÑÑ Ð½Ð° [...this.ul.children] или 104 // оÑиÑÑÐºÑ this.ul ÑеÑез while 105 // for (let ch of [].slice.call(this.ul.children)) ch.remove(); 106 this.setSelectedItemData(); 107 while (this.ul.firstChild) { 108 this.ul.removeChild(this.ul.firstChild); 109 } 110 this.ul = this.otputListItemsDrop(this.ul); 111 this.items = this.ul.children; 112 } 113 114 otputListItemsDrop(list) { 115 let i = -1; 116 const frag = document.createDocumentFragment(); 117 this.searchArray['first'] = []; 118 119 for (let item of this.options) { 120 // иÑполÑзÑеÑÑÑ ÐµÑли пеÑвÑй ÑÐ»ÐµÐ¼ÐµÐ½Ñ ÑпиÑка пÑÑÑой, Ð´Ð»Ñ <select> 121 // ÑÑÑанавливаем 'data-firstitem' Ð´Ð»Ñ ÑÐ´Ð°Ð»ÐµÐ½Ð¸Ñ ÑлеменÑа из ÑпиÑка и пÑопиÑÑÐ²Ð°Ð½Ð¸Ñ Ð½ÑлевÑÑ Ð·Ð½Ð°Ñений 122 i++; 123 if (this.s.dataset.firstitem === 'false' && i == 0) { 124 continue; 125 } else if (this.s.dataset.firstitem === 'true' && i == 0) { 126 this.txtnode.innerHTML = ''; 127 this.s.value = 0; 128 continue; 129 } 130 131 let li = document.createElement('li'); 132 // вÑÐ±Ð¾Ñ Ñона в геоÑаÑе 133 let ophtml = item.dataset.ophtml || void 0; 134 135 li.dataset.value = item.value; 136 li.innerHTML = ophtml ? ophtml : item.innerHTML; 137 138 if (item.hasAttribute('selected')) { 139 this.selected = li; 140 li.classList.add('selected'); 141 li.classList.add('current'); 142 } 143 144 this.searchArray['first'].push({ 145 value: item.value, 146 name: item.textContent, 147 }); 148 149 frag.append(li); 150 } 151 152 list.append(frag); 153 154 return list; 155 } 156 157 setSelectedItemData() { 158 if ((this.s.selectedIndex || 0) <= 0) this.s.selectedIndex = 0; 159 const curr = this.options[this.s.selectedIndex]; 160 161 for (let item of this.options) { 162 if (item == curr) item.setAttribute('selected', 'selected'); 163 else item.removeAttribute('selected'); 164 } 165
166 if (this.type === 'simple') { 167 if (curr) this.txtnode.innerHTML = curr.innerHTML; 168 } else { 169 if (!this.inputElement) return; 170 171 this.inputElement.value = curr.innerHTML; 172 this.inputElement.placeholder = curr.textContent; 173 } 174 } 175 176 setHandlers() { 177 document.addEventListener('keydown', e => this.handlers(e, 'onKeyDown')); 178 this.select.addEventListener('click', e => this.handlers(e, 'toggleDropPanel')); 179 this.s.addEventListener('change', e => this.reInitalize()); 180 this.container.addEventListener('keydown', e => this.keyControl(e, 'onKeyDown')); 181 this.ul.addEventListener('click', e => this.handlers(e, 'selectItem')); 182 183 // Ñ-Ñ Ð½Ð°Ð·Ð½Ð°Ñена пеÑеменной, ÑÑÐ¾Ð±Ñ Ð±Ñла возможноÑÑÑ ÑдалиÑÑ 184 // обÑабоÑÑик ÑобÑÑÐ¸Ñ 185 this.handler = e => this.handlers(e, 'onMouseOverItem'); 186 this.ul.addEventListener('mouseover', this.handler); 187 188 if (['simple-search', 'geo-search'].includes(this.type)) return; 189 190 this.container.addEventListener('focusout', () => { 191 setTimeout(() => this.handlers(null, 'hideDropPanel'), 250); 192 }, true); 193 } 194 195 handlers(e, fname) { 196 const fn = { 197 onKeyDown: () => { 198 if (e.code !== 'Escape') return; 199 this.handlers(null, 'hideDropPanel'); 200 }, 201 202 toggleDropPanel: () => { 203 this.arrow.classList.toggle('rotate'); 204 this.container.classList.toggle('active'); 205 206 if (this.container.matches('.active')) { 207 let sel = null; 208 for (let item of this.items) { 209 if (item.matches('.selected')) { 210 item.classList.add('current'); 211 sel = item; 212 } else { 213 item.classList.remove('current'); 214 } 215 } 216 217 this.scrollbox.initialize(); 218 if (sel) sel.scrollIntoView({ block: 'nearest' }); 219 } 220 }, 221 222 hideDropPanel: () => { 223 this.arrow.classList.remove('rotate'); 224 this.container.classList.remove('active'); 225 }, 226 227 selectItem: () => { 228 const el = this.setSelected(e); 229 if (!el) return; 230 231 this.txtnode.innerHTML = el.innerHTML; 232 233 fn.hideDropPanel(); 234 }, 235 236 onMouseOverItem: () => { 237 let el = e.target; 238 while (!el.matches('li') && (el = el.parentElement)); 239 240 for (let item of this.items) { 241 item.classList.remove('current'); 242 } 243 244 el.classList.add('current'); 245 }, 246 }; 247 248 fn[fname](); 249 } 250 251 keyControl(e, fname) { 252 const fn = { 253 onKeyDown: () => { 254 if (e.code && !['ArrowDown', 'ArrowUp', 'Enter', 'NumpadEnter'].includes(e.code)) { 255 this.container.querySelector('.scroll-content').scrollTop = 0; 256 return; 257 } 258 if (['Enter', 'NumpadEnter'].includes(e.code)) { 259 fn.selectItem(e); 260 } else if (['ArrowDown', 'ArrowUp'].includes(e.code)) { 261 fn.movementItem(e); 262 } 263 }, 264 265 getIndexForMovement: () => { 266 return [...this.items].findIndex(item => item.matches('.current')); 267 }, 268 269 movementItem: (e) => { 270 e.preventDefault(); 271 272 this.ul.removeEventListener('mouseover', this.handler); 273 274 let i = fn.getIndexForMovement(); 275 276 let next = (e.code === 'ArrowDown') ? i + 1 : i - 1; 277 // if (next < 0 || next >= this.items.length) return; 278 if (next < 0) next = this.items.length - 1; 279 if (next >= this.items.length) next = 0; 280 281 this.items[next].classList.add('current'); 282 if (i >= 0) { 283 this.items[i].classList.remove('current'); 284 } 285 286 this.items[next].scrollIntoView({ block: 'nearest' }); 287 288 setTimeout(() => this.ul.addEventListener('mouseover', this.handler), 5);
289 }, 290 291 selectItem: (e) => { 292 let i = fn.getIndexForMovement(); 293 const el = this.items[i]; 294 el.dispatchEvent(new Event('click', { bubbles: true })); 295 }, 296 }; 297 298 fn[fname](); 299 } 300 301 setSelected(e) { 302 let el = e.target; 303 while (!el.matches('li') && (el = el.parentElement)); 304 305 if (!el.matches('li') || el.dataset.search === 'empty' || el.hasAttribute('disabled')) return false; 306 307 for (let item of this.items) { 308 item.classList.remove('selected'); 309 } 310 el.classList.add('selected'); 311 312 for (let item of this.options) { 313 item.removeAttribute('selected'); 314 } 315 316 this.selected = el; 317 this.s.value = el.dataset.value; 318 319 const i = this.s.selectedIndex; 320 this.options[i].setAttribute('selected', 'selected'); 321 322 this.s.dispatchEvent(new Event('change')); 323 return el; 324 } 325 } 326 327 class SelectSimpleSearch extends SelectSimple { 328 constructor(s) { 329 super(s); 330 this.type = s.dataset.type; 331 332 this.createElementsSimpleSearch('createInitalize'); 333 this.setHandlersSimpleSearch(); 334 } 335 336 createElementsSimpleSearch(fname) { 337 const fn = { 338 createInitalize: () => { 339 this.dropPanel.id = 'dropsearch'; 340 fn.createSelectInput(); 341 }, 342 343 createSelectInput: () => { 344 this.inputElement = document.createElement('input'); 345 this.setSelectedItemData(); 346 this.select.prepend(this.inputElement); 347 348 this.txtnode.remove(); 349 }, 350 }; 351 352 fn[fname](); 353 } 354 355 outputSearchResultEmpty() { 356 if (this.ul.firstChild.dataset.search === 'empty') return; 357 358 let li = document.createElement('li'); 359 li.dataset.search = 'empty'; 360 li.innerText = lng('ÐиÑего не найдено'); 361 362 this.clearing('removeChildren'); 363 this.ul.prepend(li); 364 365 this.scrollbox.initialize(); 366 } 367 368 setHandlersSimpleSearch() { 369 this.select.addEventListener('input', e => this.getSearchResult(e)); 370 this.inputElement.addEventListener('blur', () => { 371 if (this.scrollbox.pressed) return; 372 setTimeout(() => this.handlers(null, 'hideDropPanel'), 300); 373 }); 374 } 375 376 handlers(e, fname) { 377 const fn = { 378 onKeyDown: () => { 379 super.handlers(e, fname); 380 }, 381 382 toggleDropPanel: () => { 383 if (e.target.matches('.click-zone')) return; 384 385 super.handlers(null, 'toggleDropPanel'); 386 387 if (this.container.classList.contains('active')) { 388 if (this.istags) this.clearing('removeChildrenOfTags'); 389 390 this.inputElement.value = ''; 391 this.inputElement.focus(); 392 } else { 393 this.inputElement.value = this.inputElement.placeholder; 394 this.inputElement.blur(); 395 } 396 }, 397 398 hideDropPanel: () => { 399 this.inputElement.value = this.inputElement.placeholder; 400 super.handlers(null, 'hideDropPanel'); 401 this.inputElement.blur(); 402 }, 403 404 selectItem: () => { 405 const el = this.setSelected(e); 406 if (!el) return; 407 408 this.inputElement.placeholder = el.textContent; 409 410 fn.hideDropPanel(); 411 }, 412 413 onMouseOverItem: () => { 414 super.handlers(e, 'onMouseOverItem'); 415 }, 416 }; 417 418 fn[fname](); 419 } 420 421 getSearchResult(e) { 422 this.str = e.target.value.toLowerCase().trim(); 423 424 let searchResult = this.searchArray['first'].filter(item => item.name.toLowerCase().includes(this.str)); 425 426 if (searchResult.length === 0) { 427 this.outputSearchResultEmpty(); 428 return; 429 } 430 431 const frag = document.createDocumentFragment(); 432 433 searchResult.forEach(item => { 434 if (!item.value) return; 435 436 let li = document.createElement('li'); 437 li.dataset.value = item.value; 438 li.innerHTML = `<div>${this.setHighlights(item.name)}</div>`; 439 440 frag.append(li); 441 }); 442 443 this.clearing('removeChildren'); 444 this.ul.prepend(frag); 445 this.istags = true; 446 447 this.scrollbox.initialize(); 448 } 449 450 clearing(fname) { 451 const fn = { 452 removeChildren: () => { 453 while (this.ul.firstChild) { 454 this.ul.removeChild(this.ul.firstChild); 455 } 456 }, 457 458 removeChildrenOfTags: () => { 459 fn.removeChildren(); 460 461 const frag = document.createDocumentFragment(); 462 463 for (let item of this.options) { 464 let li = document.createElement('li'); 465 li.dataset.value = item.value; 466 li.innerHTML = item.innerHTML; 467 468 if (item.value === this.selected.dataset.value) { 469 li.classList.add('active'); 470 } else { 471 li.classList.remove('active'); 472 } 473 474 frag.append(li); 475 } 476 477 this.ul.prepend(frag); 478 479 this.scrollbox.initialize(); 480 481 this.istags = false; 482 }, 483 }; 484 485 fn[fname](); 486 } 487 488 setHighlights(text) { 489 return text.replace(new RegExp(`${this.str}`, 'i'), '<b>$&</b>'); 490 } 491 } 492 493 class SelectGeoSearch extends SelectSimpleSearch { 494 constructor(s) { 495 super(s); 496 this.type = s.dataset.type; 497 this.geo = s.dataset.geo; 498 499 this.createCleaner(); 500 this.setHandlersGeoSearch(); 501 } 502 503 createCleaner() { 504 this.cleaner = document.createElement('div'); 505 this.cleaner.className = 'flex click-zone'; 506 this.arrow.before(this.cleaner); 507
508 this.dropPanel.id = 'dropgeo'; 509 } 510 511 setHandlersGeoSearch() { 512 this.cleaner.addEventListener('click', () => this.clearing('clearingSelect')); 513 this.select.addEventListener('input', e => this.searchCity(e, 'getSearchResult')); 514 } 515 516 searchCity(e, fname, data) { 517 const fn = { 518 getSearchResult: () => { 519 this.str = e.target.value.toLowerCase(); 520 const length = this.str.length; 521 522 if (length < MINREQ) { 523 fn.outpuSearchResultSingleLetter(); 524 } else if (length === MINREQ) { 525 if (this.searchArray[this.str]) { 526 this.currentSearchArray = this.searchArray[this.str]; 527 fn.outputSearchResultFromArray(this.currentSearchArray); 528 fn.createOptionSearchResult(this.currentSearchArray); 529 } else { 530 const url = this.getRequestURL(this.str, this.geo); 531 this.getJSON(url); 532 } 533 } else { 534 if (!this.currentSearchArray || this.currentSearchArray.length === 0) { 535 this.outputSearchResultEmpty(); 536 return; 537 } 538 const arr = this.currentSearchArray.filter(item => item.name.toLowerCase().includes(this.str)); 539 fn.outputSearchResultFromArray(arr); 540 } 541 }, 542 543 // вÑвод ÑезÑлÑÑаÑа поиÑка по одной бÑкве 544 outpuSearchResultSingleLetter: () => { 545 let searchResult = this.searchArray['first'].filter(item => item.name[0].toLowerCase() === this.str); 546 547 if (searchResult.length === 0) { 548 this.outputSearchResultEmpty(); 549 return; 550 } 551 552 const frag = document.createDocumentFragment(); 553 554 searchResult.forEach(item => { 555 if (!item.value) return; 556 557 let li = document.createElement('li'); 558 li.dataset.value = item.value; 559 li.innerHTML = `<div>${this.setHighlights(item.name)}</div>`; 560 561 frag.append(li); 562 }); 563 564 this.clearing('removeChildren'); 565 this.ul.prepend(frag); 566 this.istags = true; 567 568 this.scrollbox.initialize(); 569 }, 570 571 // вÑвод ÑезÑлÑÑаÑа поиÑка из ÑÐ¾Ñ ÑанÑнного маÑÑива 572 outputSearchResultFromArray: arr => { 573 if (arr.length === 0) { 574 this.outputSearchResultEmpty(); 575 return; 576 } 577 578 const frag = document.createDocumentFragment(); 579 const skip = [3159, 9908]; 580 581 arr.forEach(item => { 582 let li = document.createElement('li'); 583 584 let nameCountry = item.name_country || ''; 585 let name = ''; 586 587 if (!item.code_country) { 588 item.code_country = item.code; 589 item.code_region = item.code = 0; 590 } else 591 if (!item.code_region) { 592 item.code_region = item.code; 593 item.code = 0; 594 } 595 596 li.dataset.value = `${item.code_country},${item.code_region},${item.code}`; 597 if (nameCountry.length) { 598 li.dataset.country = nameCountry; 599 if (!skip.includes(item.code_country)) name = `, ${nameCountry}`; 600 } 601 li.innerHTML = `<div>${this.setHighlights(item.name)}${name}</div>`; 602 603 frag.append(li); 604 }); 605 606 this.clearing('removeChildren'); 607 this.ul.prepend(frag); 608 this.istags = true; 609 610 this.scrollbox.initialize(); 611 }, 612 613 // вÑвод ÑезÑлÑÑаÑа поиÑка ÑеÑез fetch 614 outputSearchResultFromData: () => { 615 fn.outputSearchResultFromArray(data); 616 fn.createOptionSearchResult(data); 617 618 this.searchArray[this.str] = data.slice(); 619 this.currentSearchArray = this.searchArray[this.str]; 620 data = []; 621 }, 622 623 createOptionSearchResult: arr => { 624 if (arr.length === 0) return; 625 626 while (this.s.firstChild) { 627 this.s.removeChild(this.s.firstChild); 628 } 629 630 const frag = document.createDocumentFragment(); 631 632 arr.forEach(item => { 633 let option = document.createElement('option'); 634 635 let codeCountry = item.code_country || 0; 636 let codeRegion = item.code_region || 0; 637 let nameCountry = item.name_country || ''; 638 639 option.value = `${codeCountry},${codeRegion},${item.code}`; 640 option.innerHTML = `${item.name}, ${nameCountry}`; 641 642 frag.append(option); 643 });
644 645 this.s.prepend(frag); 646 }, 647 }; 648 649 fn[fname](); 650 } 651 652 clearing(fname, panelIndex) { 653 const fn = { 654 clearingSelect: () => { 655 this.inputElement.value = lng('Ðе Ð¸Ð¼ÐµÐµÑ Ð·Ð½Ð°ÑениÑ'); 656 this.inputElement.placeholder = lng('Ðе Ð¸Ð¼ÐµÐµÑ Ð·Ð½Ð°ÑениÑ'); 657 this.s.value = '0,0,0'; 658 659 // добавлÑем даннÑй пÑÐ½ÐºÑ Ð² ÑпиÑок ÑезÑлÑÑаÑа поиÑка 660 const val = this.ul.firstChild.dataset.value; 661 if (val !== this.s.value) { 662 const li = document.createElement('li'); 663 664 li.dataset.value = this.s.value; 665 li.innerHTML = this.inputElement.placeholder; 666 667 this.ul.prepend(li); 668 } 669 670 const items = this.ul.childNodes; 671 for (let item of items) { 672 item.classList.remove('selected'); 673 } 674 this.ul.firstChild.classList.add('selected'); 675 676 this.scrollbox.initialize(); 677 678 super.handlers(null, 'hideDropPanel'); 679 }, 680 681 removeChildren: () => { 682 super.clearing('removeChildren', panelIndex); 683 }, 684 685 removeChildrenOfTags: () => { 686 const items = this.ul.childNodes; 687 688 for (let item of items) { 689 item.innerHTML = item.textContent; 690 } 691 this.istags = false; 692 }, 693 }; 694 695 fn[fname](); 696 } 697 698 getLocation() { 699 return this.s.value.split(','); 700 } 701 702 getRequestURL(str, geo) { 703 return `/api/a-geo/type-${geo || 9}/name-${str}`; 704 } 705 706 async getJSON(url) { 707 try { 708 const response = await fetch(url); 709 const data = await response.json(); 710 this.searchCity(null, 'outputSearchResultFromData', data.geo); 711 } catch (err) { 712 console.log(err); 713 } 714 } 715 } 716 717 // select factory 718 let selectsArr = []; 719 720 function selectsArrSearch(el) { 721 for (let sobj of selectsArr) { 722 if (sobj.s === el) return sobj; 723 } 724 return null; 725 } 726 727 function initSelect(context = document) { 728 const sel = context.querySelectorAll('select'); 729 730 for (let s of sel) { 731 if (selectsArrSearch(s)) continue; 732 733 if (s.dataset.type === 'simple-search') { 734 s = new SelectSimpleSearch(s); 735 } else if (s.dataset.type === 'geo-search') { 736 s = new SelectGeoSearch(s); 737 } else { 738 s = new SelectSimple(s); 739 } 740 selectsArr.push(s); 741 } 742 } 743 744 function reinitSelect() { 745 for (let sobj of selectsArr) { 746 sobj.reInitalize(); 747 } 748 } 749 750 // custom Scroll 751 class ScrollBox { 752 constructor(container) { 753 // return; 754 this.container = container; 755 this.contentBox = container.querySelector('.scroll-content'); 756 757 this.pressed = false; 758 this.maxheight = 200; 759 this.scrollmin = 20; 760 this.barier = 4; 761 762 this.initialize(); 763 this.registerEventsHandler(); 764 } 765 766 initialize() { 767 if (!this.scrollbar) { 768 this.scrollbar = document.createElement('div'); 769 this.scroller = document.createElement('div'); 770 771 this.scrollbar.className = 'scrollbar'; 772 this.scroller.className = 'scroller'; 773 774 this.scrollbar.append(this.scroller); 775 this.container.append(this.scrollbar); 776 } 777 778 let [ch, sh] = this.getFrameSize(); 779 // Ð²Ð¾Ð·Ð¼Ð¾Ð¶Ð½Ð°Ñ Ð¼Ð°ÐºÑималÑÐ½Ð°Ñ Ð¿ÑокÑÑÑка конÑенÑа 780 this.max = sh - ch; 781 // ÑооÑноÑение Ð¼ÐµÐ¶Ð´Ñ Ð²ÑÑоÑами вÑÑпоÑÑа и конÑенÑа 782 this.ratio = ch / sh; 783 784 this.clientHeight = ch; 785 this.scrollHeight = sh; 786 787 if (this.max > 0) { 788 this.scrollerHeight = parseInt(this.ratio * ch); 789 this.scrollerHeight = (this.scrollerHeight < this.scrollmin) ? this.scrollmin : this.scrollerHeight; 790 this.scrollbar.style.display = ''; 791 792 this.ratio = (ch - this.scrollerHeight - (this.barier * 2)) / this.max; 793 } else { 794 this.scrollbar.style.display = 'none'; 795 } 796 797 this.container.style.height = `${ch}px`; 798 this.scroller.style.height = `${this.scrollerHeight}px`; 799 this.scroller.style.top = `${this.barier}px`; 800 } 801 802 getFrameSize() { 803 let sh = 0, ch = 0; 804 let li = this.contentBox.querySelectorAll('li'); 805 for (let item of li) { 806 sh += item.getBoundingClientRect().height; 807 } 808 809 ch = (sh < this.maxheight) ? sh : this.maxheight; 810 return [ch, sh]; 811 } 812 813 registerEventsHandler() { 814 this.contentBox.addEventListener('scroll', () => { 815 this.scroller.style.top = `${this.contentBox.scrollTop * this.ratio + this.barier}px`; 816 }); 817 818 this.scroller.addEventListener('mousedown', e => { 819 this.start = e.screenY; 820 this.pressed = true; 821 }); 822 823 document.addEventListener('mousemove', e => this.drop(e)); 824 document.addEventListener('mouseup', () => this.pressed = false); 825 } 826 827 drop(e) { 828 e.preventDefault(); 829 if (this.pressed === false) return; 830 831 this.contentBox.scrollTop -= (this.start - e.screenY) / this.ratio; 832 this.start = e.screenY; 833 } 834 } 835 836 class Slider { 837 constructor(setup) { 838 this.slider = setup.slider; 839 this.width = setup.width; 840 this.title = setup.title; 841 this.range = setup.range; 842 this.index = setup.index; 843 // по длине маÑÑива опÑеделÑем кол-во ползÑнков 844 this.start = setup.start.slice(0, 2); 845 846 this.createSlider(); 847 } 848 849 createSlider() { 850 const header = document.createElement('div'); 851 header.className = 'range-header'; 852 853 const title = document.createElement('div'); 854 title.className = 'range-title'; 855 title.innerHTML = this.title; 856 857 const output = document.createElement('div'); 858 output.className = 'flex range-output'; 859 860 const control = document.createElement('div');
861 control.className = 'range-control'; 862 863 const scrollbar = document.createElement('div'); 864 scrollbar.className = 'range-scrollbar'; 865 866 const scale = document.createElement('div'); 867 scale.className = 'range-scale'; 868 869 header.append(title); 870 header.append(output); 871 scrollbar.append(scale); 872 control.append(scrollbar); 873 874 this.slider.append(header); 875 this.slider.append(control); 876 877 const elements = { output, scrollbar, scale }; 878 879 for (let i = 0, l = this.start.length; i < l; i++) { 880 const inst = new Scroller(this, elements, this.start[i], i); 881 inst.creatScroller(); 882 } 883 } 884 } 885 886 class Scroller { 887 constructor({ width, range, start, index }, { output, scrollbar, scale }, value, i) { 888 this.width = width; 889 this.min = range.min; 890 this.max = range.max; 891 this.start = start; 892 this.index = index[i]; 893 this.output = output; 894 this.scrollbar = scrollbar; 895 this.scale = scale; 896 this.value = value; 897 this.i = i; 898 this.dragObject = {}; 899 this.pressed = false; 900 this.events = false; 901 } 902 903 xpos(e) { 904 // touch event 905 if (e.targetTouches && (e.targetTouches.length >= 1)) { 906 return e.targetTouches[0].clientX; 907 } 908 // mouse event 909 return e.clientX; 910 } 911 computedX(x, min, max) { 912 return (x <= min) ? min : (x >= max) ? max : x; 913 } 914 915 creatScroller() { 916 this.input = document.createElement('input'); 917 this.input.name = this.index; 918 this.input.type = 'hidden'; 919 this.scrollbar.prepend(this.input); 920 921 this.scroller = document.createElement('div'); 922 this.scroller.className = 'range-scroller'; 923 this.scrollbar.append(this.scroller); 924 925 const sh = this.initialize(); 926 this.setStartPositon(sh); 927 } 928 929 initialize() { 930 let scrollbarWidth, halfScrollerWidth; 931 932 // еÑли range ÑкÑÑÑ 933 if (!this.scrollbar.offsetWidth) { 934 let [a, b] = this.getDataStyle(); 935 scrollbarWidth = a; 936 halfScrollerWidth = b; 937 } 938 939 this.baseWidth = this.scrollbar.offsetWidth || scrollbarWidth; 940 this.offset = this.scroller.offsetWidth / 2 || halfScrollerWidth; 941 this.ratio = this.baseWidth / (this.max - this.min); 942 943 this.registerEventsHandler(); 944 945 // поÑÑоÑннÑй Ñдвиг ползÑнка 946 let sh = (this.start.length === 1) ? this.offset : (this.i === 0) ? this.offset * 2 : 0; 947 return sh; 948 } 949 950 setStartPositon(sh) { 951 const x = (this.value - this.min) * this.ratio - sh; 952 this.scroller.style.left = `${x}px`; 953 954 this.createStartPositon(); 955 this.showScale(); 956 } 957 958 setResizePosition(sh) { 959 const v = (this.i === 0) ? this.start[0] : this.start[1]; 960 const x = (v - this.min) * this.ratio - sh; 961 962 this.scroller.style.left = `${x}px`; 963 964 this.showScale(); 965 } 966 967 getDataStyle() { 968 const root = document.querySelector('.range'); 969 const rootStyles = getComputedStyle(root); 970 const wslide = parseInt(rootStyles.getPropertyValue('--scroller-size')); 971 972 let scrollbarWidth = (this.start.length === 1) ? this.width - wslide : this.width - wslide * 2; 973 let halfScrollerWidth = wslide / 2; 974 975 return [scrollbarWidth, halfScrollerWidth]; 976 } 977 978 createStartPositon() { 979 this.span = document.createElement('span'); 980 this.span.id = this.index; 981 this.span.innerHTML = this.input.value = this.value; 982 this.output.append(this.span); 983 } 984 985 showSlideValue() { 986 this.span.innerHTML = this.input.value = this.start[this.i]; 987 } 988 989 showScale() { 990 const [x0, x1] = this.start; 991 let x, w; 992 993 if (this.start.length === 1) { 994 x = -this.offset; 995 w = (x0 - this.min) * this.ratio + 3; 996 } else { 997 x = (x0 - this.min) * this.ratio - 1; 998 w = (x1 - x0) * this.ratio + 3; 999 } 1000 1001 const st = `width: ${w}px; left: ${x}px;`; 1002 this.scale.style.cssText = st; 1003 } 1004 1005 registerEventsHandler() { 1006 if (this.events) return; 1007 1008 window.addEventListener('resize', () => { 1009 setTimeout(() => { 1010 let sh = this.initialize(); 1011 this.setResizePosition(sh); 1012 }, 26); 1013 }, false); 1014 1015 // mouse events 1016 this.scroller.addEventListener('mousedown', e => this.tap(e), false); 1017 document.addEventListener('mousemove', e => this.drag(e), false); 1018 document.addEventListener('mouseup', e => this.release(e), false); 1019 1020 // touch events 1021 this.scroller.addEventListener('touchstart', e => this.tap(e), false); 1022 document.addEventListener('touchmove', e => this.drag(e), false); 1023 document.addEventListener('touchend', e => this.release(e), false); 1024 1025 this.events = true; 1026 } 1027 1028 tap(e) { 1029 e.preventDefault(); 1030 1031 if (e.button && e.button !== 0) return; 1032 1033 this.pressed = true; 1034 this.dragObject = { el: e.target, start: this.xpos(e) }; 1035 } 1036 1037 drag(e) { 1038 if (!this.pressed) return; 1039 1040 let shiftX = this.dragObject.start - this.xpos(e); 1041 let x = this.dragObject.el.offsetLeft - shiftX; 1042 let min, max; 1043 1044 if (this.start.length === 1) { 1045 min = -this.offset; 1046 max = this.baseWidth - this.offset; 1047 1048 x = this.computedX(x, min, max); 1049 1050 this.start[0] = Math.round((x + this.offset) / this.ratio) + this.min; 1051 } else { 1052 if (this.i === 0) { 1053 min = -this.offset * 2; 1054 max = this.dragObject.el.nextElementSibling.offsetLeft - this.offset * 2; 1055 1056 x = this.computedX(x, min, max); 1057 1058 this.start[0] = Math.round((x + this.offset * 2) / this.ratio) + this.min; 1059 } else { 1060 min = this.dragObject.el.previousElementSibling.offsetLeft + this.offset * 2; 1061 max = this.baseWidth; 1062 1063 x = this.computedX(x, min, max); 1064 1065 this.start[1] = Math.round(x / this.ratio) + this.min; 1066 } 1067 } 1068 1069 this.dragObject.el.style.left = `${x}px`; 1070 this.showSlideValue(); 1071 this.showScale(); 1072 1073 this.dragObject.start = this.xpos(e); 1074 } 1075 1076 release() { 1077 this.pressed = false; 1078 this.dragObject = {}; 1079 } 1080 } 1081 1082 class SearchTags { 1083 constructor(tagscontainer, container, tagsString) { 1084 this.tagsContainer = tagscontainer; 1085 this.container = container; 1086 this.tagsString = tagsString; 1087 this.input = document.querySelector('.tag-input'); 1088 this.searchArray = []; 1089 this.currArray = []; 1090 this.tags = []; // Ñеги вÑбÑаннÑе ÑзеÑом 1091 1092 this.createElements('createInitalize'); 1093 this.setHandlers(); 1094 } 1095 1096 createElements(fname) { 1097 let panel, content; 1098 1099 const fn = { 1100 createInitalize: () => {
1101 this.dropPanel = fn.createDropPanel('drop-panel'); 1102 this.container.append(this.dropPanel); 1103 1104 this.scrollbox = new ScrollBox(this.dropPanel); 1105 }, 1106 1107 createDropPanel: () => { 1108 const [panel, content] = fn.createScrollElements(); 1109 this.contentBox = content; 1110 1111 this.ul = document.createElement('ul'); 1112 this.ul.className = 'reset options-list'; 1113 1114 content.append(this.ul); 1115 panel.append(content); 1116 1117 return panel; 1118 }, 1119 1120 createScrollElements: () => { 1121 panel = document.createElement('div'); 1122 panel.className = 'scroll-container drop-panel'; 1123 1124 content = document.createElement('div'); 1125 content.className = 'scroll-content'; 1126 1127 return [panel, content]; 1128 }, 1129 }; 1130 1131 fn[fname](); 1132 return [panel, content]; 1133 } 1134 1135 setHandlers() { 1136 document.addEventListener('keydown', e => this.handlers(e, 'onKeyDown')); 1137 this.input.addEventListener('input', () => { 1138 this.searchTags('getSearchResult'); 1139 }); 1140 1141 // Ñ-Ñ Ð½Ð°Ð·Ð½Ð°Ñена пеÑеменной, ÑÑÐ¾Ð±Ñ Ð±Ñла возможноÑÑÑ ÑдалиÑÑ 1142 // обÑабоÑÑик ÑобÑÑÐ¸Ñ 1143 this.handler = e => this.handlers(e, 'onMouseOverItem'); 1144 this.ul.addEventListener('mouseover', this.handler); 1145 this.ul.addEventListener('click', e => this.tagsControll(e, 'selectTag')); 1146 1147 this.container.addEventListener('keydown', e => this.keyControl(e, 'onKeyDown')); 1148 this.tagsContainer.addEventListener('click', e => this.tagsControll(e, 'removeTag')); 1149 1150 // this.container.addEventListener('focusout', () => this.handlers(null, 'hideDropPanel'), true); 1151 } 1152 1153 renderTags(fname, name) { 1154 const fn = { 1155 add: () => { 1156 const btn = document.createElement('div'); 1157 btn.innerHTML = name; 1158 btn.setAttribute('tag-name', name); 1159 btn.className = 'user-tag '; 1160 1161 const sp = document.createElement('span'); 1162 sp.innerHTML = 'Ã'; 1163 sp.setAttribute('data-tag', 'delete'); 1164 sp.className = 'remove-tag'; 1165 1166 btn.append(sp); 1167 this.tagsContainer.append(btn); 1168 this.handlers(null, 'hideDropPanel'); 1169 }, 1170 }; 1171 1172 fn[fname](); 1173 } 1174 1175 handlers(e, fname) { 1176 const fn = { 1177 onKeyDown: () => { 1178 if (e.code !== 'Escape') return; 1179 this.handlers(null, 'hideDropPanel'); 1180 }, 1181 1182 hideDropPanel: () => { 1183 this.container.classList.remove('active'); 1184 this.input.value = ''; 1185 }, 1186 1187 onMouseOverItem: () => { 1188 let el = e.target; 1189 1190 for (let item of this.items) { 1191 item.classList.remove('current'); 1192 } 1193 1194 el.classList.add('current'); 1195 }, 1196 }; 1197 1198 fn[fname](); 1199 } 1200 1201 keyControl(e, fname) { 1202 const fn = { 1203 onKeyDown: () => { 1204 if (e.code && !['ArrowDown', 'ArrowUp', 'Enter', 'NumpadEnter'].includes(e.code)) return; 1205 1206 if (['Enter', 'NumpadEnter'].includes(e.code)) { 1207 this.keyControl(e, 'selectItem'); 1208 } else if (['ArrowDown', 'ArrowUp'].includes(e.code)) { 1209 fn.movementItem(e); 1210 } 1211 }, 1212 1213 movementItem: (e) => { 1214 e.preventDefault(); 1215 1216 this.ul.removeEventListener('mouseover', this.handler); 1217 1218 const i = fn.getIndexForMovement(); 1219 if (i === -1) return; 1220 1221 const next = (e.code === 'ArrowDown') ? i + 1 : i - 1; 1222 if (next < 0 || next >= this.items.length) return; 1223 1224 this.items[next].classList.add('current'); 1225 if (i >= 0) { 1226 this.items[i].classList.remove('current'); 1227 } 1228 1229 this.items[next].scrollIntoView({ block: 'nearest' }); 1230 1231 setTimeout(() => this.ul.addEventListener('mouseover', this.handler), 5);
1232 }, 1233 1234 selectItem: () => { 1235 e.preventDefault(); 1236 const i = fn.getIndexForMovement(); 1237 if (i === -1) return; 1238 1239 const el = this.items[i]; 1240 if (el.hasAttribute('disabled')) return; 1241 1242 this.tagsControll(null, 'selectTag', el); 1243 }, 1244 1245 getIndexForMovement: () => { 1246 return [...this.items].findIndex(item => item.matches('.current')); 1247 }, 1248 }; 1249 1250 fn[fname](); 1251 } 1252 1253 searchTags(fname, data) { 1254 const fn = { 1255 getSearchResult: () => { 1256 this.str = this.input.value.toLowerCase().trim(); 1257 const length = this.str.length; 1258 1259 if (length < MINREQTAG) { 1260 this.container.classList.remove('active'); 1261 return; 1262 } 1263 1264 const str = this.str.slice(0, 3); 1265 1266 if (!this.searchArray[str]) { 1267 // полÑÑаем Ð¾Ñ ÑеÑвеÑа ÑпиÑок ÑÑÑеÑÑвÑÑÑÐ¸Ñ Ñегов 1268 const url = `/api/a-getusertags/tagsearch/tagname-${encodeURIComponent(str)}`; 1269 this.serverRequest('getusertags', url, str); 1270 } else { 1271 fn.selectOutputSearchResult(str); 1272 } 1273 }, 1274 1275 selectOutputSearchResult: str => { 1276 str = str ? str : data; 1277 1278 this.currArray = this.searchArray[str].filter(tag => tag.name.toLowerCase().includes(this.str)); 1279 1280 if (this.currArray.length === 0) { 1281 fn.outputSearchResultEmpty(); 1282 } else { 1283 fn.outputSearchResultFromArray(); 1284 } 1285 }, 1286 1287 disableSearchTags: () => { 1288 const str = this.str.slice(0, 3); 1289 for (let t of this.searchArray[str]) { 1290 if (t.id == data.id) t.disable = 1; 1291 } 1292 }, 1293 1294 // вÑвод ÑезÑлÑÑаÑа поиÑка из ÑÐ¾Ñ ÑанÑнного маÑÑива 1295 outputSearchResultFromArray: () => { 1296 if (this.currArray.length === 0) { 1297 fn.outputSearchResultEmpty(); 1298 return; 1299 } 1300 1301 const frag = document.createDocumentFragment(); 1302 const classname = this.constructor.name; 1303 1304 for (let item of this.currArray) { 1305 if (classname === 'Tags') { 1306 if (item.disable == 1) continue; 1307 } 1308 1309 const li = document.createElement('li'); 1310 li.setAttribute('tagid', item.id); 1311 li.innerHTML = item.name; 1312 li.innerHTML = `<div>${this.setHighlights(item.name)}</div>`; 1313 1314 frag.append(li); 1315 } 1316 1317 if (classname === 'Tags') { 1318 let li = fn.createNewTagItem(classname); 1319 if (li) frag.append(li);; 1320 } 1321 1322 this.removeChildren(); 1323 this.ul.prepend(frag); 1324 1325 if (!this.ul.firstChild) { 1326 this.container.classList.remove('active'); 1327 return; 1328 } else { 1329 this.ul.firstChild.classList.add('current'); 1330 } 1331 1332 this.scrollbox.initialize(); 1333 1334 this.items = this.ul.children; 1335 this.container.classList.add('active'); 1336 }, 1337 1338 outputSearchResultEmpty: () => { 1339 const classname = this.constructor.name; 1340 let li = fn.createNewTagItem(classname); 1341 1342 this.removeChildren(); 1343 if (li) this.ul.prepend(li); 1344 1345 this.scrollbox.initialize(); 1346 1347 this.items = this.ul.children; 1348 this.container.classList.add('active'); 1349 }, 1350 1351 createNewTagItem: classname => { 1352 const arr = this.currArray.filter(tag => tag.name.toLowerCase() == this.str); 1353 if (arr.length !== 0) return false; 1354 1355 const li = document.createElement('li'); 1356 li.setAttribute('tagid', 'new_tag'); 1357 1358 if (classname === 'Tags') { 1359 li.innerHTML = '<div>' + lng('ÐобавиÑÑ @').replace('@', `«<b>${this.str}</b>» `) + lng('как инÑеÑеÑ...') + '</div>'; 1360 } else { 1361 li.classList.add('current'); 1362 li.innerHTML = '<div>' + lng('ÐобавиÑÑ @').replace('@', `«<b>${this.str}</b>»`) + '</div>'; 1363 } 1364 1365 return li; 1366 } 1367 }; 1368 1369 fn[fname](); 1370 } 1371 1372 tagsControll(e, fname, li) { 1373 const fn = { 1374 selectTag: () => { 1375 let tag = [], name; 1376 1377 let el = e ? e.target : li; 1378 const id = fn.getTagID(el, 'li'); 1379 1380 if (!id) return; 1381 1382 if (id == 'new_tag') { 1383 while (!el.matches('li') && (el = el.parentElement)); 1384 1385 name = el.querySelector('b').innerHTML; 1386 tag.push({ name }); 1387 } else { 1388 tag = this.currArray.filter(tag => tag.id == id); 1389 name = tag[0].name; 1390 } 1391 fn.checkTag(name); 1392 }, 1393 1394 removeTag: () => { 1395 const el = e.target; 1396 if (!el.dataset.tag || el.dataset.tag !== 'delete') return; 1397 1398 const tag = el.parentElement; 1399 const name = tag.getAttribute('tag-name'); 1400 1401 const i = this.tags.indexOf(name); 1402 if (i === -1) return; 1403 1404 this.tags.splice(i, 1); 1405 fn.updateTagsString(); 1406 fn.updateSearchTags(name); 1407 1408 tag.remove(); 1409 }, 1410 1411 getTagID: (el, parent) => { 1412 while (!el.matches(parent) && (el = el.parentElement)); 1413 return (!el.matches(parent)) ? false : el.getAttribute('tagid'); 1414 }, 1415 1416 updateTagsString: () => { 1417 this.tagsString.value = this.tags.join(';'); 1418 this.tagsString.disabled = !this.tagsString.value; 1419 }, 1420 1421 updateSearchTags: name => { 1422 if (Object.keys(this.searchArray).length === 0) return; 1423 1424 for (let key in this.searchArray) { 1425 for (let tag of this.searchArray[key]) { 1426 if (tag.name == name) tag.disable = 0; 1427 } 1428 } 1429 }, 1430 1431 checkTag: (name) => { 1432 if (this.tags.includes(name)) { 1433 this.handlers(null, 'hideDropPanel'); 1434 return; 1435 } 1436 1437 this.tags.push(name); 1438 fn.updateTagsString(); 1439 this.renderTags('add', name); 1440 }, 1441 }; 1442 1443 fn[fname](); 1444 } 1445
1446 serverRequest(fname, url, str) { 1447 const fn = { 1448 getusertags: async () => { 1449 try { 1450 const response = await fetch(url); 1451 const data = await response.json(); 1452 1453 this.searchArray[str] = [...data.tags]; 1454 1455 this.searchTags('selectOutputSearchResult', str); 1456 } catch (err) { 1457 console.log(err); 1458 } 1459 }, 1460 }; 1461 1462 fn[fname](); 1463 } 1464 1465 setHighlights(text) { 1466 return text.replace(new RegExp(`${this.str}`, 'i'), '<b>$&</b>'); 1467 } 1468 1469 removeChildren(list = this.ul) { 1470 if (list === undefined) return; 1471 while (list.firstChild) { 1472 list.removeChild(list.firstChild); 1473 } 1474 } 1475 }; 1476 1477 class Tags extends SearchTags { 1478 constructor(tagscontainer, container, tagtmpl, tags) { 1479 super(tagscontainer, container); 1480 1481 this.tagtmpl = tagtmpl; 1482 this.userTags = tags; 1483 this.p = document.getElementById('notags'); 1484 1485 this.renderTags('updateTagsContainer'); 1486 super.setHandlers(); 1487 this.setHandlers(); 1488 } 1489 1490 renderTags(fname, tag, edit = false) { 1491 const fn = { 1492 updateTagsContainer: () => { 1493 this.removeChildren(this.tagsContainer); 1494 1495 this.tmpl = new mustache(this.tagtmpl, { 1496 f_moder: d => { 1497 const tagModeration = [ 1498 'not-moderated', 1499 'allowed', 1500 'declined', 1501 ]; 1502 let moder = d.allowed < 3 ? d.allowed : 2; 1503 return tagModeration[moder]; 1504 }, 1505 }); 1506 1507 for (let tag of this.userTags) { 1508 fn.add(tag); 1509 } 1510 if (this.userTags.length === 0) fn.add({ empty: 1 }); 1511 }, 1512 1513 add: t => { 1514 tag = t || tag; 1515 tag.edit = edit; 1516 go(this.tagsContainer).append(this.tmpl.parse(tag)); 1517 }, 1518 }; 1519 1520 fn[fname](); 1521 } 1522 1523 setHandlers() { 1524 let el = document.querySelector('#end_edit_tags .btn'); 1525 if (el) el.addEventListener('click', () => this.handlers(null, 'hideDropPanel')); 1526 } 1527 1528 tagsControll(e, fname, li) { 1529 const fn = { 1530 selectTag: () => { 1531 let el = e ? e.target : li; 1532 while (!el.matches('li') && (el = el.parentElement)); 1533 const id = fn.getTagID(el, 'li'); 1534 if (!id) return; 1535 1536 if (id == 'new_tag') { 1537 const name = encodeURIComponent(el.querySelector('b').innerHTML); 1538 const url = `/api/a-newusertags/tags-[{'name':'${name}', 'category':11}]`; 1539 this.serverRequest('newusertags', url); 1540 } else { 1541 const url = `/api/a-addusertags/tagids-[${id}]`; 1542 this.serverRequest('addusertags', url); 1543 } 1544 }, 1545 1546 removeTag: () => { 1547 let el = e.target; 1548 if (!el.dataset.tag || el.dataset.tag !== 'delete') return; 1549 1550 const id = fn.getTagID(el, 'div'); 1551 if (!id) return; 1552 1553 const url = `/api/a-delusertags/tagids-[${id}]`; 1554 this.serverRequest('delusertags', url, id); 1555 }, 1556 1557 getTagID: (el, parent) => { 1558 while (!el.matches(parent) && (el = el.parentElement)); 1559 return (!el.matches(parent)) ? false : el.id ? el.id : el.getAttribute('tagid'); 1560 }, 1561 }; 1562 1563 fn[fname](); 1564 } 1565 1566 searchTags(fname, data) { 1567 const fn = { 1568 getSearchResult: () => { 1569 super.searchTags('getSearchResult'); 1570 }, 1571 1572 // ÑоÑмиÑÑем маÑÑив Ñ ÑезÑлÑÑаÑами поиÑка, 1573 // оÑÑилÑÑÑовÑÐ²Ð°Ñ ÑÑÑеÑÑвÑÑÑие Ñ ÑзеÑа Ñеги 1574 filterSearchTags: () => { 1575 const str = this.str.slice(0, 3); 1576 let userTagsID = this.userTags.map(tag => tag.id); 1577 this.searchArray[str] = []; 1578 1579 for (let tag of data.tags) { 1580 if (userTagsID.length !== 0) { 1581 if (userTagsID.includes(tag.id)) tag.disable = 1; 1582 } 1583 this.searchArray[str].push(tag); 1584 } 1585 1586 this.currArray = [...this.searchArray[str]]; 1587 1588 super.searchTags('outputSearchResultFromArray'); 1589 }, 1590 1591 updateSearchTags: () => { 1592 if (Object.keys(this.searchArray).length === 0) return; 1593 1594 for (let key in this.searchArray) { 1595 for (let tag of this.searchArray[key]) { 1596 if (tag.name == data) delete tag.disable; 1597 } 1598 } 1599 }, 1600 }; 1601 1602 fn[fname](); 1603 } 1604 1605 serverRequest(fname, url, id) { 1606 const fn = { 1607 getusertags: async () => { 1608 try { 1609 const response = await fetch(url); 1610 const data = await response.json(); 1611 1612 this.searchTags('filterSearchTags', data); 1613 } catch (err) { 1614 console.log(err); 1615 } 1616 }, 1617 1618 addusertags: async () => { 1619 try { 1620 const response = await fetch(url); 1621 const data = await response.json(); 1622 1623 const tag = data.tags[0]; 1624 tag.search = encodeURIComponent(tag.name); 1625 1626 this.userTags.push(tag); 1627 super.searchTags('disableSearchTags', tag); 1628 this.renderTags('updateTagsContainer', tag, true); 1629 1630 super.handlers(null, 'hideDropPanel'); 1631 } catch (err) { 1632 console.log(err); 1633 } 1634 }, 1635 1636 delusertags: async () => { 1637 try { 1638 await fetch(url); 1639 1640 const tag = this.userTags.filter(tag => tag.id == id); 1641 this.userTags = this.userTags.filter(tag => tag.id != id); 1642 1643 this.renderTags('updateTagsContainer', tag[0], true); 1644 this.searchTags('updateSearchTags', tag[0].name); 1645 } catch (err) { 1646 console.log(err); 1647 } 1648 }, 1649 1650 newusertags: async () => { 1651 try { 1652 const response = await fetch(url); 1653 const data = await response.json(); 1654 1655 const tag = data.tags[0]; 1656 tag.search = encodeURIComponent(tag.name); 1657 1658 1659 this.userTags.push(tag); 1660 this.renderTags('updateTagsContainer', tag, true); 1661 1662 this.handlers(null, 'hideDropPanel'); 1663 } catch (err) { 1664 console.log(err); 1665 } 1666 }, 1667 }; 1668 1669 fn[fname](); 1670 } 1671 } 1672 1673 class Gallery { 1674 // наÑÑÑойки по-ÑмолÑÐ°Ð½Ð¸Ñ 1675 static defaults = { 1676 margin: 10, // ÑаÑÑÑоÑние Ð¼ÐµÐ¶Ð´Ñ ÑлеменÑами [px] 1677 visibleItems: 1, // ÑколÑко ÑлеменÑов показÑваÑÑ Ð¾Ð´Ð½Ð¾Ð²Ñеменно 1678 param: null, // Ð¸Ð½Ð´ÐµÐºÑ ÑлеменÑа, Ñ ÐºÐ¾ÑоÑого наÑнÑÑÑÑ Ð¿Ð¾ÐºÐ°Ð· 1679 from: null, // на какой ÑÑÑаниÑе бÑла вÑзвана галеÑÐµÑ 1680 border: 0, // ÑолÑина Ñамки изобÑÐ°Ð¶ÐµÐ½Ð¸Ñ Ð¿ÑопиÑÐ°Ð½Ð½Ð°Ñ Ð² CSS [px] 1681 responsive: true, // адапÑÐ¸Ð²Ð½Ð°Ñ Ð³Ð°Ð»ÐµÑÐµÑ 1682 autoScroll: false, // авÑомаÑиÑеÑкое пÑокÑÑÑивание 1683 interval: 3000, // задеÑжка пÑи авÑомаÑиÑеÑком пÑокÑÑÑивании [ms] 1684 nav: true, // показаÑÑ/ÑкÑÑÑÑ ÐºÐ½Ð¾Ð¿ÐºÐ¸ next/prev 1685 dots: true, // показаÑÑ/ÑкÑÑÑÑ Ð¿Ð¾ÑÑÑаниÑнÑÑ Ð½Ð°Ð²Ð¸Ð³Ð°ÑÐ¸Ñ 1686 keyControl: false, // ÑпÑавление клавиÑами впÑаво / влево 1687 animated: false, // вклÑÑение анимаÑии 1688 baseTransition: 0.4, // ÑкоÑоÑÑÑ Ð°Ð½Ð¸Ð¼Ð°Ñии, пÑи изменении CSS ÑвойÑÑв 1689 delayTimer: 250, // вÑÐµÐ¼Ñ Ð·Ð°Ð´ÐµÑжки пÑи resize ÑÑÑаниÑÑ [ms] 1690 limit: 30 // огÑаниÑиваем пеÑемеÑение кÑÐ°Ð¹Ð½Ð¸Ñ ÑлеменÑов [px] 1691 }; 1692 1693 constructor(setup) { 1694 this.gallery = setup.gallery; 1695 this.widthSliderDefault = setup.widthSlider; 1696 this.setup = setup; 1697 this.from = setup.from; 1698 1699 // конÑÐµÐ¹Ð½ÐµÑ Ð² коÑоÑом оÑобÑажаÑÑÑÑ ÑлеменÑÑ Ð³Ð°Ð»ÐµÑеи 1700 this.slider = this.gallery.querySelector('.slider'); 1701 // конÑейнеÑ, непоÑÑедÑÑвенно в коÑоÑом ÑаÑÐ¿Ð¾Ð»Ð¾Ð¶ÐµÐ½Ñ ÑлеменÑÑ ÑлайдеÑа 1702 this.stage = this.gallery.querySelector('.stage'); 1703 // ÑлеменÑÑ ÑлайдеÑа 1704 this.items = this.gallery.querySelectorAll('.stage > div'); 1705 // колиÑеÑÑво ÑлеменÑов в ÑлайдеÑе 1706 this.count = this.items.length; 1707 1708 this.current = 0; // index кооÑдинаÑÑ ÑекÑÑего ÑлеменÑа 1709 this.next = 0; // index кооÑдинаÑÑ ÑледÑÑÑего ÑлеменÑа 1710 this.__pressed = false; 1711 this.__events = false; 1712 this.start = 0; // кооÑдинаÑа, Ñ ÐºÐ¾ÑоÑой наÑаÑо пеÑеÑаÑкивание 1713 this.shift = 0; // на ÑколÑко бÑл пеÑемеÑÑн кÑÑÑÐ¾Ñ Ð¾ÑноÑиÑелÑно start 1714 1715 setTimeout(() => { this.init(); }, 20); 1716 } 1717 1718 // обÑединÑÐµÑ Ð·Ð½Ð°ÑÐµÐ½Ð¸Ñ Ð´Ð²ÑÑ Ð¾Ð±ÑекÑов и вÑдаÑÑ Ð¾Ð±Ñий ÑезÑлÑÑÐ°Ñ 1719 static extend(out) { 1720 out = out || {}; 1721 1722 for (let i = 1; i < arguments.length; i++) { 1723 if (!arguments[i]) continue; 1724 for (let key in arguments[i]) { 1725 if (arguments[i].hasOwnProperty(key)) out[key] = arguments[i][key]; 1726 } 1727 } 1728 1729 console.log('out: ', out); 1730 return out; 1731 }; 1732 1733 static xpos(e) { 1734 if (e.targetTouches && (e.targetTouches.length >= 1)) { 1735 return e.targetTouches[0].clientX; 1736 } 1737 1738 // mouse event 1739 return e.clientX; 1740 } 1741 1742 init() { 1743 this.options = Gallery.extend({}, Gallery.defaults, this.setup);
1744 1745 this.setSizeCarousel(); 1746 this.setCoordinates(); 1747 this.createControl('initControl'); 1748 1749 if (this.__events) return; 1750 1751 this.setHandlers(); 1752 1753 if (this.options.param) { 1754 this.next = this.options.param * this.options.visibleItems; 1755 const x = this.coordinates[this.next]; 1756 1757 this.scroll(x, 0); 1758 1759 this.options.param = null; 1760 } 1761 } 1762 1763 setSizeCarousel() { 1764 // вÑÑпоÑÑ, в коÑоÑом пÑокÑÑÑиваÑÑÑÑ ÑлеменÑÑ Ð³Ð°Ð»ÐµÑеи 1765 this.widthSlider = this.slider.offsetWidth || this.widthSliderDefault; 1766 1767 if (this.options.responsive) this.setAdaptiveOptions(); 1768 1769 this.max = this.count - this.options.visibleItems; 1770 1771 // ÑиÑина ÑлеменÑа ÑлайдеÑа 1772 const w = (this.widthSlider - this.options.margin * (this.options.visibleItems - 1)) / this.options.visibleItems; 1773 1774 this.width = w + this.options.margin; 1775 this.widths = this.width * this.count; 1776 1777 this.stage.style.width = this.widths + 'px'; 1778 1779 for (let [index, item] of this.items.entries()) { 1780 if (this.from && item.hasAttribute('data-href') && typeof evproc != 'undefined') { 1781 item.addEventListener('click', e => { 1782 evproc.fb('story_click', { from: this.from, story_number: index + 1 }); 1783 }); 1784 1785 item.dataset.href = `${item.dataset.href}/from-${this.from}`; 1786 } 1787 1788 item.style.cssText = `width:${w}px; margin-right:${this.options.margin}px;`; 1789 } 1790 1791 // поÑле Ñого, как каÑÐºÐ°Ñ Ð³Ð°Ð»ÐµÑеи поÑÑÑоен, вÑе ÑазмеÑÑ ÑлеменÑов 1792 // вÑÑиÑÐ»ÐµÐ½Ñ Ð¸ пÑопиÑÐ°Ð½Ñ Ð² ÑÑилÑÑ , делаем каÑÑÑÐµÐ»Ñ Ð²Ð¸Ð´Ð¸Ð¼Ð¾Ð¹ ÑеÑез 1793 // ÑвойÑÑво ÑÑÐ¸Ð»Ñ 'visibility' 1794 setTimeout(() => { this.gallery.style.visibility = 'visible'; }, 180); 1795 } 1796 1797 setCoordinates() { 1798 let point = 0; 1799 this.coordinates = []; 1800 1801 while (this.coordinates.length < this.count) { 1802 this.coordinates.push(point); 1803 point -= this.width; 1804 } 1805 } 1806 1807 setAdaptiveOptions() { 1808 // ÑÐ°Ð·Ð¼ÐµÑ Ð²Ð¸Ð´Ð¸Ð¼Ð¾Ð¹ ÑаÑÑи окна бÑаÑзеÑа 1809 const width = document.documentElement.clientWidth; 1810 // маÑÑив Ñ ÐºÐ¾Ð½ÑÑолÑнÑми ÑоÑками 1811 const points = []; 1812 // ÑекÑÑÐ°Ñ ÐºÐ¾Ð½ÑÑолÑÐ½Ð°Ñ ÑоÑка 1813 let point; 1814 1815 for (let key in this.options.adaptive) { 1816 points.push(key); 1817 } 1818 1819 for (let i = 0, j = points.length; i < j; i++) { 1820 let a = points[i], 1821 b = (points[i + 1] !== undefined) ? points[i + 1] : points[i]; 1822 1823 if (width <= points[0]) { 1824 point = points[0]; 1825 } else if (width >= a && width < b) { 1826 point = a; 1827 } else if (width >= points[points.length - 1]) { 1828 point = points[points.length - 1]; 1829 } 1830 } 1831 1832 const setting = this.options.adaptive[point]; 1833 for (let key in setting) { 1834 this.options[key] = setting[key]; 1835 } 1836 } 1837 1838 createControl(fname) { 1839 const fn = { 1840 initControl: () => { 1841 this.navCtrl = this.gallery.querySelector('.nav-ctrl'); 1842 this.dotsCtrl = this.gallery.querySelector('.dots-ctrl'); 1843 1844 if (this.options.nav === true) { 1845 this.btnPrev = this.navCtrl.querySelector('[data-shift=prev]'); 1846 this.btnNext = this.navCtrl.querySelector('[data-shift=next]'); 1847 1848 fn.setNavStyle(); 1849 1850 this.navCtrl.dataset.hidden = false; 1851 } else { 1852 this.navCtrl.dataset.hidden = true; 1853 } 1854 1855 if (this.options.dots === true) { 1856 fn.creatDotsCtrl(); 1857 this.dotsCtrl.dataset.hidden = false; 1858 } else { 1859 this.dotsCtrl.dataset.hidden = true; 1860 } 1861 }, 1862 1863 setNavStyle: () => { 1864 this.btnPrev.classList.remove('disable'); 1865 this.btnNext.classList.remove('disable'); 1866 1867 if (this.current === 0) { 1868 this.btnPrev.classList.add('disable'); 1869 } else if (this.current >= this.count - this.options.visibleItems) { 1870 this.btnNext.classList.add('disable'); 1871 } 1872 }, 1873 1874 creatDotsCtrl: () => { 1875 this.spots = []; 1876 this.dotsCtrl.innerHTML = ''; 1877 1878 const frag = document.createDocumentFragment(); 1879 let i = 0, point = 0; 1880 1881 while (i < this.count) { 1882 const li = document.createElement('li'); 1883 frag.append(li); 1884 this.spots.push(li); 1885 1886 i += this.options.visibleItems; 1887 point = (i <= this.max) ? point - this.width * this.options.visibleItems : -this.width * this.max; 1888 } 1889
1890 this.dotsCtrl.prepend(frag); 1891 fn.setDotsStyle(); 1892 }, 1893 1894 setDotsStyle: () => { 1895 for (let spot of this.spots) { 1896 spot.classList.remove('active'); 1897 } 1898 1899 let i = this.options.param ? this.options.param : (this.next < this.max) ? Math.trunc(this.next / this.options.visibleItems) : this.spots.length - 1; 1900 this.spots[i].classList.add('active'); 1901 }, 1902 }; 1903 1904 fn[fname](); 1905 } 1906 1907 setHandlers(e) { 1908 window.addEventListener('resize', () => this.handlers(null, 'resize')); 1909 if (this.options.autoScroll) this.handlers(null, 'autoScroll'); 1910 this.navCtrl.addEventListener('click', e => this.handlers(e, 'navControl')); 1911 this.dotsCtrl.addEventListener('click', e => this.handlers(e, 'dotsControl')); 1912 if (this.options.keyControl) { 1913 window.addEventListener('keydown', e => this.handlers(e, 'keyControl')); 1914 } 1915 1916 // mouse events 1917 // ÑолÑко, еÑли ÑказаÑÐµÐ»Ñ Ð¼ÑÑи Ð½Ð°Ñ Ð¾Ð´Ð¸ÑÑÑ Ð½Ð°Ð´ галеÑеей 1918 this.gallery.querySelector('.slider').addEventListener('wheel', e => this.handlers(e, 'wheelControl')); 1919 1920 // touch events 1921 this.stage.addEventListener('touchstart', e => this.handlers(e, 'tap')); 1922 this.stage.addEventListener('touchmove', e => this.handlers(e, 'drag')); 1923 this.stage.addEventListener('touchend', e => this.handlers(e, 'release')); 1924 1925 this.__events = true; 1926 } 1927 1928 handlers(e, fname) { 1929 const fn = { 1930 resize: () => { 1931 clearTimeout(this.resizeTimer); 1932 this.resizeTimer = setTimeout(() => { 1933 this.init(); 1934 1935 this.current = (this.current <= this.max) ? this.current : this.max; 1936 1937 let x = this.coordinates[this.current]; 1938 this.scroll(x, this.options.baseTransition); 1939 }, this.options.delayTimer); 1940 }, 1941 1942 autoScroll: () => { 1943 let tm = setInterval(() => { 1944 let x = this.getNextCoordinates(1); 1945 if (this.next == this.count - 1) { 1946 this.options.autoScroll = false; 1947 clearInterval(tm); 1948 }; 1949 this.scroll(x, this.options.baseTransition); 1950 }, this.options.interval); 1951 }, 1952 1953 navControl: () => { 1954 const el = e.target; 1955 if (!el.matches('span')) return; 1956 1957 // -1 - prev, 1 - next 1958 const d = (el.dataset.shift === 'next') ? 1 : -1; 1959 const x = this.getNextCoordinates(d); 1960 1961 this.scroll(x, this.options.baseTransition); 1962 }, 1963 1964 dotsControl: () => { 1965 const el = e.target; 1966 1967 if (!el.matches('li') || el.classList.contains('active')) return; 1968 1969 const i = this.spots.indexOf(el); 1970 if (i == -1) return; 1971 1972 this.next = i * this.options.visibleItems; 1973 this.next = (this.next <= this.max) ? this.next : this.max; 1974 1975 const x = this.coordinates[this.next]; 1976 const n = Math.abs(this.current - this.next); 1977 const t = this.options.baseTransition + n * 0.07; 1978 1979 this.scroll(x, t); 1980 }, 1981 1982 keyControl: () => { 1983 if (e.code && !['ArrowRight', 'ArrowLeft']) return; 1984 const d = (e.code === 'ArrowRight') ? 1 : -1; 1985 const x = this.getNextCoordinates(d); 1986 1987 this.scroll(x, this.options.baseTransition); 1988 }, 1989 1990 wheelControl: () => { 1991 e.preventDefault(); 1992 const d = (e.deltaY > 0) ? 1 : -1; 1993 const x = this.getNextCoordinates(d); 1994 1995 this.scroll(x, this.options.baseTransition); 1996 }, 1997 1998 tap: () => { 1999 // e.preventDefault(); 2000 e.stopPropagation(); 2001 2002 this.start = Gallery.xpos(e); 2003 this.shift = 0; 2004 this.__pressed = true; 2005 }, 2006 2007 drag: () => { 2008 e.preventDefault(); 2009 e.stopPropagation(); 2010 2011 if (this.__pressed === false) return; 2012 2013 this.shift = this.start - Gallery.xpos(e); 2014 2015 if (Math.abs(this.shift) < 3) return; 2016 if (this.current === 0 && this.shift < 0 || this.current == this.max && this.shift > 0) return; 2017 2018 const remaining = this.widths - this.width * this.options.visibleItems; 2019 const delta = this.coordinates[this.current] - this.shift; 2020 2021 if (delta >= 0 || Math.abs(delta) - remaining >= 0) return; 2022 2023 this.scroll(delta, 0); 2024 }, 2025 2026 release: () => { 2027 if (Math.abs(this.shift) >= 3) { 2028 e.preventDefault(); 2029 e.stopPropagation(); 2030 } 2031 2032 if (this.__pressed === false) return; 2033 2034 let d = null; 2035 if (Math.abs(this.shift) > this.width / 10) { 2036 d = this.shift > 0 ? Math.ceil(this.shift / this.width) : Math.floor(this.shift / this.width); 2037 } 2038 2039 const x = this.getNextCoordinates(d); 2040 2041 this.scroll(x, this.options.baseTransition); 2042 2043 this.__pressed = false; 2044 }, 2045 }; 2046 2047 fn[fname](); 2048 } 2049 2050 /* ÐÑокÑÑÑивание галеÑеи */ 2051 getNextCoordinates(direction) { 2052 if (!direction) return this.coordinates[this.current]; 2053 2054 // direction - напÑавление пеÑемеÑениÑ: -1 - left, 1 - right 2055 if (this.options.autoScroll && this.current >= this.count - this.options.visibleItems) { 2056 this.next = 0; 2057 } else { 2058 if (this.current === 0 && direction <= -1 || (this.current >= this.max) && direction >= 1) return; 2059 this.next += direction; 2060 this.next = (this.next > this.max) ? this.max : (this.next < 0) ? 0 : this.next; 2061 } 2062 2063 return this.coordinates[this.next]; 2064 } 2065 2066 scroll(x, transition) { 2067 if (typeof (x) !== 'number') return; 2068 2069 this.stage.style.cssText = `width:${this.widths}px; transform:translateX(${x}px); transition:${transition}s`; 2070 2071 this.current = (this.next < this.max) ? this.next : this.max; 2072 2073 if (this.options.nav) this.createControl('setNavStyle'); 2074 if (this.options.dots) this.createControl('setDotsStyle'); 2075 } 2076 }; 2077 2078 // ÐниÑиализаÑÐ¸Ñ ÐºÐ¾Ð½ÑÑоллеÑов 2079 2080 function controllInitalize() { 2081 initSelect(); 2082 } 2083 2084 function controllRefrash() { 2085 reinitSelect(); 2086 } 2087 2088 function controllSlider(setup) { 2089 new Slider(setup); 2090 } 2091 2092 function controllSearchTags(tagscontainer, container, tagsString) { 2093 new SearchTags(tagscontainer, container, tagsString); 2094 } 2095 2096 function controllTags(tagscontainer, container, tagtmpl, tags) { 2097 new Tags(tagscontainer, container, tagtmpl, tags); 2098 } 2099 2100 function controllGallery(setup) { 2101 new Gallery(setup); 2102 } 2103 2104 return { controllInitalize, controllRefrash, controllSlider, controllTags, controllSearchTags, controllGallery }; 2105})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.