1document.addEventListener('DOMContentLoaded', function () { 2 function escapeHtml(value) { 3 return String(value || '') 4 .replace(/&/g, '&') 5 .replace(/</g, '<') 6 .replace(/>/g, '>') 7 .replace(/"/g, '"') 8 .replace(/'/g, '''); 9 } 10 11 function collectItems(previewLink) { 12 const grid = previewLink.closest('.reply-attachments-grid'); 13 const links = grid 14 ? Array.from(grid.querySelectorAll('.reply-attachment-link[data-preview-type]')) 15 : [previewLink]; 16 const items = links.map(function (link) { 17 const tile = link.closest('.reply-attachment-tile'); 18 const downloadLink = tile ? tile.querySelector('.reply-attachment-download') : null; 19 20 return { 21 type: link.getAttribute('data-preview-type') || 'document', 22 url: link.getAttribute('data-preview-url') || '', 23 name: link.getAttribute('data-preview-name') || 'Файл', 24 ext: link.getAttribute('data-preview-ext') || '', 25 downloadUrl: downloadLink ? (downloadLink.getAttribute('href') || '') : (link.getAttribute('data-preview-url') || ''), 26 }; 27 }); 28 29 return { 30 items: items, 31 index: Math.max(0, links.indexOf(previewLink)), 32 }; 33 } 34 35 function ensureModal() { 36 let modal = document.getElementById('replyAttachmentPreviewModal'); 37 if (modal) { 38 return modal; 39 } 40 41 modal = document.createElement('div'); 42 modal.id = 'replyAttachmentPreviewModal'; 43 modal.className = 'reply-attachment-modal'; 44 modal.innerHTML = ` 45 <div class="reply-attachment-modal-backdrop" data-close="1"></div> 46 <div class="reply-attachment-modal-dialog" role="dialog" aria-modal="true"> 47 <div class="reply-attachment-modal-top"> 48 <div class="reply-attachment-modal-title"></div> 49 <div class="reply-attachment-modal-actions"> 50 <a class="reply-attachment-modal-download" href="#" target="_blank" rel="noopener" download aria-label="СкаÑаÑÑ"> 51 <i class="fa fa-download" aria-hidden="true"></i> 52 </a> 53 <button type="button" class="reply-attachment-modal-close" data-close="1" aria-label="ÐакÑÑÑÑ"> 54 <i class="fa fa-times" aria-hidden="true"></i> 55 </button> 56 </div> 57 </div> 58 <button type="button" class="reply-attachment-modal-nav is-prev" data-nav="-1" aria-label="ÐÑедÑдÑÑее"> 59 <i class="fa fa-angle-left" aria-hidden="true"></i> 60 </button> 61 <button type="button" class="reply-attachment-modal-nav is-next" data-nav="1" aria-label="СледÑÑÑее"> 62 <i class="fa fa-angle-right" aria-hidden="true"></i> 63 </button> 64 <div class="reply-attachment-modal-body"></div> 65 </div> 66 `; 67 document.body.appendChild(modal); 68 return modal; 69 } 70 71 function renderCurrent(modal) { 72 if (!modal || !modal._replyAttachmentState || !modal._replyAttachmentState.items.length) { 73 return; 74 } 75 76 const state = modal._replyAttachmentState; 77 const item = state.items[state.index]; 78 const body = modal.querySelector('.reply-attachment-modal-body'); 79 const title = modal.querySelector('.reply-attachment-modal-title'); 80 const downloadButton = modal.querySelector('.reply-attachment-modal-download'); 81 const prevButton = modal.querySelector('.reply-attachment-modal-nav.is-prev'); 82 const nextButton = modal.querySelector('.reply-attachment-modal-nav.is-next'); 83 const safeName = escapeHtml(item.name); 84 const safeExt = escapeHtml(item.ext); 85 86 if (!body || !title || !downloadButton) { 87 return; 88 } 89 90 title.textContent = item.name; 91 92 const dlIcon = downloadButton.querySelector('i'); 93 if (item.type === 'portfolio') { 94 downloadButton.setAttribute('href', item.downloadUrl || '#'); 95 downloadButton.removeAttribute('download'); 96 if (dlIcon) dlIcon.className = 'fa fa-external-link'; 97 downloadButton.setAttribute('aria-label', 'ÐÑкÑÑÑÑ ' + item.name); 98 } else { 99 downloadButton.setAttribute('href', item.downloadUrl || item.url || '#'); 100 downloadButton.setAttribute('download', ''); 101 if (dlIcon) dlIcon.className = 'fa fa-download'; 102 downloadButton.setAttribute('aria-label', 'СкаÑаÑÑ ' + item.name); 103 } 104 105 if (item.type === 'image') { 106 body.innerHTML = ` 107 <div class="reply-attachment-preview-image-wrap"> 108 <img src="${item.url}" alt="${safeName}" class="reply-attachment-preview-image"> 109 </div> 110 `; 111 } else if (item.type === 'portfolio') { 112 if (item.url) { 113 body.innerHTML = ` 114 <div class="reply-attachment-preview-portfolio"> 115 <img src="${item.url}" alt="${safeName}" class="reply-attachment-preview-portfolio-img"> 116 <div class="reply-attachment-preview-portfolio-foot">
117 <span class="reply-attachment-preview-portfolio-bar-name"> 118 <i class="fa fa-briefcase" aria-hidden="true"></i> ${safeName} 119 </span> 120 <a class="btn btn-default btn-xs" href="${item.downloadUrl}" target="_blank" rel="noopener">ÐÑкÑÑÑÑ Ð¿ÑоекÑ</a> 121 </div> 122 </div> 123 `; 124 } else { 125 body.innerHTML = ` 126 <div class="reply-attachment-preview-doc"> 127 <div class="reply-attachment-preview-doc-icon"><i class="fa fa-briefcase" aria-hidden="true"></i></div> 128 <div class="reply-attachment-preview-doc-name">${safeName}</div> 129 <a class="btn btn-default btn-sm m-t-1" href="${item.downloadUrl}" target="_blank" rel="noopener">ÐÑкÑÑÑÑ Ð¿ÑоекÑ</a> 130 </div> 131 `; 132 } 133 } else { 134 body.innerHTML = ` 135 <div class="reply-attachment-preview-doc"> 136 <div class="reply-attachment-preview-doc-icon"><i class="fa fa-file-o" aria-hidden="true"></i></div> 137 <div class="reply-attachment-preview-doc-name">${safeName}</div> 138 <div class="reply-attachment-preview-doc-ext">${safeExt}</div> 139 <a class="btn btn-default btn-sm m-t-1" href="${item.url}" target="_blank" rel="noopener">ÐÑкÑÑÑÑ</a> 140 </div> 141 `; 142 } 143 144 if (prevButton && nextButton) { 145 const hideNav = state.items.length < 2; 146 prevButton.classList.toggle('is-hidden', hideNav); 147 nextButton.classList.toggle('is-hidden', hideNav); 148 } 149 } 150 151 function step(modal, delta) { 152 if (!modal || !modal._replyAttachmentState || !modal._replyAttachmentState.items.length) { 153 return; 154 } 155 156 const state = modal._replyAttachmentState; 157 const length = state.items.length; 158 state.index = (state.index + delta + length) % length; 159 renderCurrent(modal); 160 } 161 162 function closeModal(modal) { 163 if (!modal) { 164 return; 165 } 166 modal.classList.remove('is-visible'); 167 document.body.classList.remove('reply-attachment-modal-open'); 168 } 169 170 document.addEventListener('click', function (event) { 171 const previewLink = event.target.closest('.reply-attachment-link'); 172 const modal = document.getElementById('replyAttachmentPreviewModal'); 173 174 if (previewLink) { 175 const previewType = previewLink.getAttribute('data-preview-type'); 176 // Ðез Ñипа или поÑÑÑолио â не пеÑÐµÑ Ð²Ð°ÑÑваем: href оÑкÑоеÑÑÑ Ð½Ð°Ñивно 177 if (!previewType || previewType === 'portfolio') { 178 return; 179 } 180 event.preventDefault(); 181 const currentModal = ensureModal(); 182 currentModal._replyAttachmentState = collectItems(previewLink); 183 renderCurrent(currentModal); 184 currentModal.classList.add('is-visible'); 185 document.body.classList.add('reply-attachment-modal-open'); 186 return; 187 } 188 189 if (modal && event.target.closest('[data-nav]')) { 190 event.preventDefault(); 191 const navControl = event.target.closest('[data-nav]'); 192 const delta = parseInt(navControl.getAttribute('data-nav') || '0', 10); 193 if (delta) { 194 step(modal, delta); 195 } 196 return; 197 } 198 199 if (modal && event.target.closest('[data-close="1"]')) { 200 closeModal(modal); 201 } 202 }); 203 204 document.addEventListener('keydown', function (event) { 205 const modal = document.getElementById('replyAttachmentPreviewModal'); 206 if (!modal || !modal.classList.contains('is-visible')) { 207 return; 208 } 209 210 if (event.key === 'Escape') { 211 closeModal(modal); 212 return; 213 } 214 215 if (event.key === 'ArrowLeft') { 216 event.preventDefault(); 217 step(modal, -1); 218 return; 219 } 220 221 if (event.key === 'ArrowRight') { 222 event.preventDefault(); 223 step(modal, 1); 224 } 225 }); 226});
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.