PageSourceSearch

https://www.nakivo.com/res/js/new/modals/modal-main.js

js nakivo.com collected 2026-09-24 15:21:12 UTC 8,817 bytes, 276 lines download raw bytes

1// Modal for wistia video
2
3const modalHtml = `
4  <div class="modal-main | js-modal-main">
5    <div class="modal-main__in | js-modal-main-backdrop">
6      <div class="modal-main__main">
7  
8        <div class="modal-main__content | js-modal-main-to"></div>
9  
10        <div class="modal-main__close js-modal-main-close">
11          <svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg"><g id="Group"><path id="Vector" d="M12 2L2 12" stroke="#7F9AB9" stroke-width="2" stroke-linecap="square" stroke-linejoin="round"/><path id="Vector_2" d="M2 2L12 12" stroke="#7F9AB9" stroke-width="2" stroke-linecap="square" stroke-linejoin="round"/></g>
12          </svg>
13        </div>
14  
15      </div>
16    </div>
17  </div>
18  
19  <div class="modal-main-backdrop | js-modal-backdrop"></div>
20`;
21
22document.body.appendChild(document.createRange().createContextualFragment(modalHtml));
23
24const modal = (elem, target) => {
25  const modalEl = document.querySelector(elem);
26  const backdrop = document.querySelector('.js-modal-backdrop');
27
28  let contentFrom;
29  const contentTo = modalEl.querySelector(`${elem}-to`);
30  const modalMain = modalEl.querySelector('.modal-main__main');
31  const modalInner = modalEl.querySelector('.modal-main__in');
32  const closeButton = document.querySelector(`${elem}-close`);
33  const defaultVideoRatio = 16 / 9;
34  let currentVideoRatio = defaultVideoRatio;
35  let resizeHandler = null;
36
37  const getPaddingTopByRatio = (ratio) => {
38    if (!ratio || ratio <= 0) {
39      return '56.25%';
40    }
41
42    return `${(1 / ratio) * 100}%`;
43  };
44
45  const getProviderMeta = (videoId) => {
46    if (videoId.includes('youtube') || videoId.includes('youtu.be')) {
47      let id = null;
48
49      try {
50        const urlObj = new URL(videoId);
51
52        // https://www.youtube.com/watch?v=ID
53        id = urlObj.searchParams.get('v');
54
55        if (!id) {
56          // https://youtu.be/ID, https://www.youtube.com/embed/ID,
57          // https://www.youtube.com/shorts/ID, https://www.youtube.com/v/ID
58          const match = urlObj.pathname.match(/\/(?:embed|shorts|v)\/([^/?#]+)|^\/([^/?#]+)/);
59          id = match ? (match[1] || match[2]) : null;
60        }
61      } catch (e) {
62        // not a full URL — fall back to extracting an id from the raw string
63        const match = videoId.match(/(?:v=|embed\/|shorts\/|youtu\.be\/)([^&/?#]+)/);
64        id = match ? match[1] : null;
65      }
66
67      if (!id) {
68        return null;
69      }
70
71      return {
72        type: 'youtube',
73        id,
74        embedSrc: `https://www.youtube.com/embed/${id}`,
75        oembedUrl: `https://www.youtube.com/oembed?url=${encodeURIComponent(`https://www.youtube.com/watch?v=${id}`)}&format=json`
76      };
77    }
78
79    if (videoId.includes('vimeo')) {
80      const regex = /vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^\/]*)\/videos\/|album\/(\d+)\/video\/|video\/|)(\d+)(?:$|\/|\?)/;
81      const match = videoId.match(regex);
82      const id = match ? match[3] : null;
83
84      if (!id) {
85        return null;
86      }
87
88      return {
89        type: 'vimeo',
90        id,
91        embedSrc: `https://player.vimeo.com/video/${id}`,
92        oembedUrl: `https://vimeo.com/api/oembed.json?url=${encodeURIComponent(`https://vimeo.com/${id}`)}`
93      };
94    }
95
96    return {
97      type: 'wistia',
98      id: videoId,
99      embedSrc: `https://fast.wistia.net/embed/iframe/${videoId}?videoFoam=true`,
100      oembedUrl: `https://fast.wistia.net/oembed?url=${encodeURIComponent(`https://home.wistia.com/medias/${videoId}`)}`
101    };
102  };
103
104  const getVideoRatio = async (providerMeta) => {
105    if (!providerMeta || !providerMeta.oembedUrl) {
106      return null;
107    }
108
109    try {
110      const res = await fetch(providerMeta.oembedUrl);
111
112      if (!res.ok) {
113        return null;
114      }
115
116      const data = await res.json();
117      const width = Number(data.width);
118      const height = Number(data.height);
119
120      if (width > 0 && height > 0) {
121        return width / height;
122      }
123    } catch (e) {}
124
125    return null;
126  };
127
128  const getVideoEmbedConfig = (providerMeta) => {
129    const paddingTop = getPaddingTopByRatio(defaultVideoRatio);
130
131    const responsiveWrapper = (iframe) => `
132      <div class="js-video-responsive-padding" style="position:relative;padding:${paddingTop} 0 0 0;">
133        <div style="width:100%;height:100%;position:absolute;top:0;left:0;">
134          ${iframe}
135        </div>
136      </div>
137    `;
138
139    const iframe = (src) => `
140      <iframe src="${src}" width="100%" height="100%" allowfullscreen style="width:100%;height:100%;border:none;"></iframe>
141    `;
142
143    if (!providerMeta || providerMeta.type === 'youtube' || providerMeta.type === 'vimeo') {
144      return { html: responsiveWrapper(iframe(providerMeta ? providerMeta.embedSrc : '')) };
145    }
146
147    // Wistia default
148    return {
149      html: `
150        <div class="wistia_responsive_padding js-video-responsive-padding" style="padding:${paddingTop} 0 0 0;position:relative;"><div class="wistia_responsive_wrapper" style="height:100%;left:0;position:absolute;top:0;width:100%;"><iframe src="${providerMeta.embedSrc}" allow="autoplay; fullscreen" allowtransparency="true" frameborder="0" scrolling="no" class="wistia_embed" name="wistia_embed" allowfullscreen msallowfullscreen width="100%" height="100%"></iframe></div>
151        </div>
152        <script src="https://fast.wistia.net/assets/external/E-v1.js" async></script>
153      `
154    };
155  };
156
157  const fitVideoToViewport = (ratio) => {
158    if (!modalMain || !modalInner || !ratio || ratio <= 0) {
159      return;
160    }
161
162    const innerStyles = window.getComputedStyle(modalInner);
163    const horizontalPadding = parseFloat(innerStyles.paddingLeft) + parseFloat(innerStyles.paddingRight);
164    const verticalPadding = parseFloat(innerStyles.paddingTop) + parseFloat(innerStyles.paddingBottom);
165
166    const availableWidth = Math.max(window.innerWidth - horizontalPadding, 320);
167    const availableHeight = Math.max(window.innerHeight - verticalPadding, 180);
168
169    let fittedWidth = Math.min(availableWidth, 970);
170    let fittedHeight = fittedWidth / ratio;
171
172    if (fittedHeight > availableHeight) {
173      fittedHeight = availableHeight;
174      fittedWidth = fittedHeight * ratio;
175    }
176
177    modalMain.style.width = `${Math.floor(fittedWidth)}px`;
178    modalMain.style.maxWidth = 'none';
179  };
180
181  const bindViewportResize = () => {
182    if (resizeHandler) {
183      window.removeEventListener('resize', resizeHandler);
184    }
185
186    resizeHandler = () => {
187      fitVideoToViewport(currentVideoRatio);
188    };
189
190    window.addEventListener('resize', resizeHandler);
191    resizeHandler();
192  };
193
194  const unbindViewportResize = () => {
195    if (resizeHandler) {
196      window.removeEventListener('resize', resizeHandler);
197      resizeHandler = null;
198    }
199
200    modalMain.style.width = '';
201    modalMain.style.maxWidth = '';
202  };
203
204  const applyVideoRatio = async (providerMeta) => {
205    const ratio = await getVideoRatio(providerMeta);
206    const responsivePadding = contentTo.querySelector('.js-video-responsive-padding');
207
208    if (!responsivePadding || !ratio) {
209      return;
210    }
211
212    currentVideoRatio = ratio;
213    responsivePadding.style.padding = `${getPaddingTopByRatio(ratio)} 0 0 0`;
214    fitVideoToViewport(currentVideoRatio);
215  };
216
217  const open = () => {
218    modalEl.classList.add('open');
219    backdrop.classList.add('show');
220  };
221
222  const close = () => {
223    modalEl.classList.remove('open');
224    backdrop.classList.remove('show');
225    unbindViewportResize();
226    currentVideoRatio = defaultVideoRatio;
227    if (contentFrom) {
228      contentFrom.classList.add('hidden');
229      document.body.appendChild(contentFrom);
230    }
231    setTimeout(() => contentTo.innerHTML = '', 200);
232  };
233
234  closeButton.addEventListener('click', close);
235  backdrop.addEventListener('click', close);
236
237  document.addEventListener('click', (e) => {
238    if (e.target.classList.contains((`${elem}-backdrop`).slice(1))) {
239      close();
240    }
241  });
242
243  document.documentElement.addEventListener('keydown', (e) => {
244    if (e.key === 'Escape') {
245      close();
246    }
247  });
248
249  // init
250  if (target.dataset.videoId) {
251    const videoId = target.dataset.videoId;
252    const providerMeta = getProviderMeta(videoId);
253    const embedConfig = getVideoEmbedConfig(providerMeta);
254
255    contentTo.appendChild(document.createRange().createContextualFragment(embedConfig.html));
256    currentVideoRatio = defaultVideoRatio;
257    bindViewportResize();
258    applyVideoRatio(providerMeta);
259  } else {
260    contentFrom = document.querySelector(`.${target.dataset.modal}`);
261
262    if (contentFrom) {
263      contentFrom.classList.remove('hidden');
264      contentTo.appendChild(contentFrom);
265    }
266  }
267
268  open();
269};
270
271document.querySelectorAll('.js-modal-main-open').forEach((btn) => {
272  btn.addEventListener('click', (e) => {
273    e.preventDefault();
274    modal('.js-modal-main', btn);
275  });
276});

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.