PageSourceSearch

https://www.kibrisinsesi.com/themes/flow/assets/js/flow-story.js?v=6.9.5

js kibrisinsesi.com collected 2026-10-07 19:41:40 UTC 13,435 bytes, 451 lines download raw bytes

1/* ==========================================================================
2   FLOW STORY - Haber Sitesi Story Komponenti
3   Kategori bazlı, Swiper entegreli, mobil odaklı
4   Nova CMS Entegrasyonu
5   ========================================================================== */
6
7(function() {
8  'use strict';
9
10  // Yapılandırma
11  const CONFIG = {
12    duration: 5000,           // Her story için süre (ms)
13    localStorageKey: 'flow-story-seen',
14    autoClose: true           // Son story'de otomatik kapat
15  };
16
17  // Global değişkenler
18  let modal = null;
19  let categorySwiper = null;
20  let storySwiper = null;
21  let currentCategory = 0;
22  let currentStory = 0;
23  let progressTimer = null;
24  let isPaused = false;
25  let storiesData = [];
26
27  // LocalStorage yardımcıları
28  const storage = {
29    get: function() {
30      try {
31        return JSON.parse(localStorage.getItem(CONFIG.localStorageKey)) || {};
32      } catch (e) {
33        return {};
34      }
35    },
36    set: function(data) {
37      try {
38        localStorage.setItem(CONFIG.localStorageKey, JSON.stringify(data));
39      } catch (e) {}
40    },
41    markSeen: function(categoryId, storyId) {
42      const data = this.get();
43      if (!data[categoryId]) data[categoryId] = [];
44      if (!data[categoryId].includes(storyId)) {
45        data[categoryId].push(storyId);
46        this.set(data);
47      }
48    },
49    isSeen: function(categoryId, storyId) {
50      const data = this.get();
51      return data[categoryId] && data[categoryId].includes(storyId);
52    },
53    isCategorySeen: function(categoryId, totalStories) {
54      const data = this.get();
55      return data[categoryId] && data[categoryId].length >= totalStories;
56    }
57  };
58
59  // Zaman formatlama
60  function timeAgo(timestamp) {
61    const now = Date.now();
62    const diff = now - timestamp * 1000;
63    const minutes = Math.floor(diff / 60000);
64    const hours = Math.floor(diff / 3600000);
65    const days = Math.floor(diff / 86400000);
66
67    if (minutes < 1) return 'Az önce';
68    if (minutes < 60) return minutes + ' dk önce';
69    if (hours < 24) return hours + ' saat önce';
70    if (days < 7) return days + ' gün önce';
71    return new Date(timestamp * 1000).toLocaleDateString('tr-TR');
72  }
73
74  // Modal HTML oluştur
75  function createModal() {
76    const modalEl = document.createElement('div');
77    modalEl.className = 'flow-story-modal';
78    modalEl.id = 'flow-story-modal';
79    modalEl.innerHTML = `
80      <div class="flow-story-header">
81        <div class="flow-story-progress"></div>
82        <div class="flow-story-info">
83          <div class="flow-story-info-left">
84            <img class="flow-story-info-avatar" src="" alt="">
85            <div>
86              <div class="flow-story-info-name"></div>
87              <div class="flow-story-info-time"></div>
88            </div>
89          </div>
90          <button class="flow-story-close" aria-label="Kapat">&times;</button>
91        </div>
92      </div>
93      <div class="flow-story-content">
94        <div class="swiper flow-story-swiper">
95          <div class="swiper-wrapper"></div>
96        </div>
97        <div class="flow-story-touch prev"></div>
98        <div class="flow-story-touch next"></div>
99        <button class="flow-story-nav prev"><i class="fa fa-chevron-left"></i></button>
100        <button class="flow-story-nav next"><i class="fa fa-chevron-right"></i></button>
101      </div>
102    `;
103    document.body.appendChild(modalEl);
104    return modalEl;
105  }
106
107  // Progress bar'ları oluştur
108  function createProgressBars(count) {
109    const container = modal.querySelector('.flow-story-progress');
110    container.innerHTML = '';
111    for (let i = 0; i < count; i++) {
112      const bar = document.createElement('div');
113      bar.className = 'flow-story-progress-bar';
114      bar.innerHTML = '<div class="fill"></div>';
115      container.appendChild(bar);
116    }
117  }
118
119  // Progress bar'ları güncelle
120  function updateProgressBars(activeIndex) {
121    const bars = modal.querySelectorAll('.flow-story-progress-bar');
122    bars.forEach((bar, i) => {
123      bar.classList.remove('active', 'seen');
124      if (i < activeIndex) {
125        bar.classList.add('seen');
126      } else if (i === activeIndex) {
127        bar.classList.add('active');
128      }
129    });
130  }
131
132  // Header bilgilerini güncelle
133  function updateHeader(category, story) {
134    modal.querySelector('.flow-story-info-avatar').src = category.cover;
135    modal.querySelector('.flow-story-info-name').textContent = category.name;
136    modal.querySelector('.flow-story-info-time').textContent = timeAgo(story.time);
137  }
138
139  // Slide HTML oluştur
140  function createSlideHTML(story) {
141    return `
142      <div class="swiper-slide flow-story-slide" data-story-id="${story.id}" data-link="${story.link}">
143        <img class="media" src="${story.image}" alt="${story.title}" loading="eager">
144        <a href="${story.link}" class="flow-story-overlay">
145          <div class="flow-story-title">${story.title}</div>
146          <span class="flow-story-link">
147            Haberi Oku <i class="fa fa-arrow-right"></i>
148          </span>
149        </a>
150      </div>
151    `;
152  }
153
154  // Swiper'ı başlat/güncelle
155  function initSwiper(category) {
156    const wrapper = modal.querySelector('.swiper-wrapper');
157    wrapper.innerHTML = category.stories.map(createSlideHTML).join('');
158
159    if (storySwiper) {
160      storySwiper.destroy(true, true);
161    }
162
163    storySwiper = new Swiper('.flow-story-swiper', {
164      allowTouchMove: true,
165      speed: 300,
166      on: {
167        slideChange: function() {
168          currentStory = this.activeIndex;
169          const story = category.stories[currentStory];
170
171          // Header güncelle
172          updateHeader(category, story);
173
174          // Progress güncelle
175          updateProgressBars(currentStory);
176
177          // Görüldü işaretle
178          storage.markSeen(category.id, story.id);
179
180          // Timer'ı yeniden başlat
181          startTimer();
182        }
183      }
184    });
185  }
186
187  // Timer başlat
188  function startTimer() {
189    stopTimer();
190    if (isPaused) return;
191
192    const category = storiesData[currentCategory];
193    const story = category.stories[currentStory];
194    const duration = (story.duration || CONFIG.duration);
195
196    // CSS animasyon süresini ayarla
197    modal.style.setProperty('--story-duration', duration + 'ms');
198
199    progressTimer = setTimeout(() => {
200      nextStory();
201    }, duration);
202  }
203
204  // Timer durdur
205  function stopTimer() {
206    if (progressTimer) {
207      clearTimeout(progressTimer);
208      progressTimer = null;
209    }
210  }
211
212  // Sonraki story
213  function nextStory() {
214    const category = storiesData[currentCategory];
215
216    if (currentStory < category.stories.length - 1) {
217      // Aynı kategoride sonraki story
218      storySwiper.slideNext();
219    } else if (currentCategory < storiesData.length - 1) {
220      // Sonraki kategori
221      currentCategory++;
222      currentStory = 0;
223      loadCategory(currentCategory);
224    } else if (CONFIG.autoClose) {
225      // Son story, modal'ı kapat
226      closeModal();
227    }
228  }
229
230  // Önceki story
231  function prevStory() {
232    if (currentStory > 0) {
233      // Aynı kategoride önceki story
234      storySwiper.slidePrev();
235    } else if (currentCategory > 0) {
236      // Önceki kategori
237      currentCategory--;
238      const prevCategory = storiesData[currentCategory];
239      currentStory = prevCategory.stories.length - 1;
240      loadCategory(currentCategory, currentStory);
241    }
242  }
243
244  // Kategoriyi yükle
245  function loadCategory(categoryIndex, startStory = 0) {
246    const category = storiesData[categoryIndex];
247
248    // Progress bar'ları oluştur
249    createProgressBars(category.stories.length);
250
251    // Swiper'ı başlat
252    initSwiper(category);
253
254    // Başlangıç story'sine git
255    if (startStory > 0) {
256      storySwiper.slideTo(startStory, 0);
257    }
258
259    // İlk story için header güncelle
260    updateHeader(category, category.stories[startStory]);
261    updateProgressBars(startStory);
262
263    // İlk story'yi görüldü işaretle
264    storage.markSeen(category.id, category.stories[startStory].id);
265
266    // Timer başlat
267    startTimer();
268  }
269
270  // Modal'ı aç
271  function openModal(categoryIndex) {
272    if (!modal) {
273      modal = createModal();
274      bindEvents();
275    }
276
277    currentCategory = categoryIndex;
278    currentStory = 0;
279    isPaused = false;
280
281    loadCategory(categoryIndex);
282
283    modal.classList.add('active');
284    document.body.style.overflow = 'hidden';
285  }
286
287  // Modal'ı kapat
288  function closeModal() {
289    if (!modal) return;
290
291    stopTimer();
292    modal.classList.remove('active');
293    document.body.style.overflow = '';
294
295    // Timeline'daki görüldü durumlarını güncelle
296    updateTimelineSeen();
297  }
298
299  // Timeline'daki görüldü durumlarını güncelle
300  function updateTimelineSeen() {
301    document.querySelectorAll('.flow-story-item').forEach(item => {
302      const categoryId = item.dataset.categoryId;
303      const category = storiesData.find(c => c.id === categoryId);
304      if (category && storage.isCategorySeen(categoryId, category.stories.length)) {
305        item.classList.add('seen');
306      }
307    });
308  }
309
310  // Event listener'ları bağla
311  function bindEvents() {
312    // Kapat butonu
313    modal.querySelector('.flow-story-close').addEventListener('click', closeModal);
314
315    // Sol touch alanı - önceki story
316    modal.querySelector('.flow-story-touch.prev').addEventListener('click', function(e) {
317      e.preventDefault();
318      e.stopPropagation();
319      prevStory();
320    });
321
322    // Sağ touch alanı - sonraki story
323    modal.querySelector('.flow-story-touch.next').addEventListener('click', function(e) {
324      e.preventDefault();
325      e.stopPropagation();
326      nextStory();
327    });
328
329    // Nav butonları (masaüstü)
330    modal.querySelector('.flow-story-nav.prev').addEventListener('click', function(e) {
331      e.preventDefault();
332      e.stopPropagation();
333      prevStory();
334    });
335    modal.querySelector('.flow-story-nav.next').addEventListener('click', function(e) {
336      e.preventDefault();
337      e.stopPropagation();
338      nextStory();
339    });
340
341    // Pause/Resume (basılı tutma) - sadece görsel alanında
342    let pressTimer;
343    modal.addEventListener('touchstart', function(e) {
344      // Overlay, link veya close butonuna dokunulursa pause yapma
345      if (e.target.closest('.flow-story-overlay') || e.target.closest('.flow-story-close')) return;
346
347      pressTimer = setTimeout(() => {
348        isPaused = true;
349        modal.classList.add('paused');
350        stopTimer();
351      }, 200);
352    });
353
354    modal.addEventListener('touchend', function() {
355      clearTimeout(pressTimer);
356      if (isPaused) {
357        isPaused = false;
358        modal.classList.remove('paused');
359        startTimer();
360      }
361    });
362
363    // Escape tuşu
364    document.addEventListener('keydown', function(e) {
365      if (e.key === 'Escape' && modal.classList.contains('active')) {
366        closeModal();
367      }
368      if (e.key === 'ArrowRight' && modal.classList.contains('active')) {
369        nextStory();
370      }
371      if (e.key === 'ArrowLeft' && modal.classList.contains('active')) {
372        prevStory();
373      }
374    });
375  }
376
377  /**
378   * Zuck Story formatını Flow Story formatına dönüştür
379   * Nova CMS'deki get_story_posts() fonksiyonu Zuck formatında veri döndürüyor
380   * Bu fonksiyon o veriyi Flow Story formatına çevirir
381   *
382   * Zuck format:
383   * { id, photo, name, items: [{ id, type, length, src, preview, link, linkText, time }] }
384   *
385   * Flow format:
386   * { id, name, cover, stories: [{ id, title, image, link, time, duration }] }
387   */
388  function convertZuckToFlowFormat(zuckData) {
389    if (!zuckData || !Array.isArray(zuckData)) return [];
390
391    return zuckData
392      .filter(category => category && category.items && category.items.length > 0)
393      .map(category => ({
394        id: category.id,
395        name: category.name,
396        cover: category.photo,
397        stories: category.items.map(item => ({
398          id: item.id,
399          title: item.linkText || '',
400          image: item.src || item.preview,
401          link: item.link,
402          time: item.time,
403          duration: (item.length || 10) * 1000 // length saniye cinsinden, ms'ye çevir
404        }))
405      }));
406  }
407
408  // Timeline'ı başlat
409  function initTimeline(data) {
410    // Veriyi Flow formatına dönüştür (Zuck formatından geliyorsa)
411    if (data && data.length > 0 && data[0].items) {
412      // Zuck formatı algılandı, dönüştür
413      storiesData = convertZuckToFlowFormat(data);
414    } else {
415      // Zaten Flow formatında
416      storiesData = data;
417    }
418
419    if (!storiesData || storiesData.length === 0) return;
420
421    // Timeline item'larına click event ekle
422    document.querySelectorAll('.flow-story-item').forEach((item) => {
423      const categoryId = item.dataset.categoryId;
424      const idx = storiesData.findIndex(c => c.id === categoryId);
425
426      // Eşleşen veri yoksa thumb'ı gizle (boş kategoriler tıklanamaz olmasın diye render edilse de)
427      if (idx < 0) {
428        item.style.display = 'none';
429        return;
430      }
431
432      item.addEventListener('click', () => openModal(idx));
433
434      // Görüldü durumunu kontrol et
435      if (storage.isCategorySeen(categoryId, storiesData[idx].stories.length)) {
436        item.classList.add('seen');
437      }
438    });
439  }
440
441  // Global API
442  window.FlowStory = {
443    init: initTimeline,
444    open: openModal,
445    close: closeModal,
446    config: function(options) {
447      Object.assign(CONFIG, options);
448    }
449  };
450
451})();

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.