Marhaban ya Ramadhan
`;
shadow.appendChild(tpl.content.cloneNode(true));
// ELEMENTS (shadow-scoped)
const $ = sel => shadow.querySelector(sel);
const frame = $('.wf2-frame');
const shell = $('.wf2-shell');
const toggle = $('.wf2-toggle');
const badge = $('.wf2-badge');
const articlesWrap = $('#wf2Articles');
const filterInput = $('#wf2Filter');
const preview = $('#wf2Preview');
const previewTitle = $('#wf2PreviewTitle');
const previewExcerpt = $('#wf2PreviewExcerpt');
const previewRead = $('#wf2PreviewRead');
const collapseBtn = shadow.querySelector('[data-action="collapse"]');
const statusEl = $('#wf2Status');
const welcomeEl = $('#wf2Welcome');
// CONFIG
const CONFIG = {
feedUrl: 'https://www.warkasa1919.com/feeds/posts/default?alt=json-in-script&max-results=8',
pollIntervalMs: 30000,
localKeys: { state: 'wf2_state_v2', lastRead: 'wf2_lastread_v2' },
initialCollapsed: true,
maxArticles: 8,
jsonpTimeout: 8000
};
let ARTICLES = [];
let lastRead = new Date(0);
let pollTimer = null;
let activeJsonp = null; // {cbName, script}
// Utilities
const esc = s => String(s).replace(/[&<>'"`]/g, c=>({"&":"&","<":"<",">":">","'":"'","\"":""","`":"`"})[c]);
const safeDate = s => { try{ const d = new Date(s); return isNaN(d.getTime()) ? new Date(0) : d; }catch(e){ return new Date(0); } };
function cleanupJsonp(cbName, script){ if(script) script.remove(); if(cbName) try{ delete window[cbName]; }catch(e){ window[cbName] = undefined; } activeJsonp = null; }
// JSONP helper (unique callback)
function jsonpFetch(url, timeout=CONFIG.jsonpTimeout){
return new Promise((resolve, reject)=>{
if(activeJsonp) cleanupJsonp(activeJsonp.cbName, activeJsonp.script);
const cbName = '__wf2_cb_' + Math.random().toString(36).slice(2);
const script = document.createElement('script');
let timer = null;
const finish = (data, err) => { clearTimeout(timer); cleanupJsonp(cbName, script); if(err) reject(err); else resolve(data); };
window[cbName] = data => finish(data);
timer = setTimeout(()=> finish(null, new Error('timeout')), timeout);
script.src = url + (url.includes('?') ? '&' : '?') + 'callback=' + cbName;
script.async = true;
script.onerror = ()=> finish(null, new Error('error'));
document.head.appendChild(script);
activeJsonp = { cbName, script };
});
}
// parse blogger JSON -> simple items
function parseBlogger(json){
try{
const posts = (json.feed && json.feed.entry) ? json.feed.entry.slice(0, CONFIG.maxArticles) : [];
return posts.map((e, i)=>{
const title = e.title && e.title.$t ? e.title.$t : ('Artikel ' + (i+1));
const content = e.content ? e.content.$t : (e.summary ? e.summary.$t : '');
const excerpt = content.replace(/<[^>]+>/g,'').slice(0,140).trim();
let url = '';
if (e.link && e.link.length){ const alt = e.link.find(l=>l.rel==='alternate'); url = alt ? alt.href : e.link[0].href; }
const date = e.published ? safeDate(e.published.$t || e.published) : new Date();
return { id: e.id ? (e.id.$t||('p'+i)) : ('p'+i), title, excerpt, url, date };
});
}catch(e){ return []; }
}
// render
function render(filter=''){
articlesWrap.innerHTML = '';
const q = (filter||'').toLowerCase().trim();
const frag = document.createDocumentFragment();
let shown = 0;
ARTICLES.forEach(a=>{
if (q && !(a.title.toLowerCase().includes(q) || a.excerpt.toLowerCase().includes(q))) return;
const el = document.createElement('div'); el.className = 'wf2-article'; el.tabIndex = 0; el.dataset.url = a.url;
el.innerHTML = '';
if (a.date > lastRead){
const n = document.createElement('span'); n.className = 'wf2-badge'; n.textContent = 'BARU';
n.style.cssText = 'position:absolute; right:8px; top:8px; background:#47ff57;'; // badge BARU hijau
el.appendChild(n);
}
frag.appendChild(el); shown++;
});
if (!shown){
const m=document.createElement('div'); m.style.padding='8px'; m.style.color='var(--c4)'; m.textContent='Tidak ditemukan artikel.';
articlesWrap.appendChild(m);
} else {
articlesWrap.appendChild(frag);
}
}
// open preview
function openPreview(item){
previewTitle.textContent = item.title;
previewExcerpt.textContent = item.excerpt;
previewRead.href = item.url;
preview.setAttribute('aria-hidden','false');
previewRead.focus();
}
function closePreview(){
preview.setAttribute('aria-hidden','true');
filterInput.focus();
}
// badge update
function updateBadge(){
const unread = ARTICLES.filter(a=>a.date>lastRead).length;
badge.textContent = unread || '';
badge.setAttribute('aria-hidden', unread ? 'false' : 'true');
}
// persistence
function loadState(){
try{
const s = localStorage.getItem(CONFIG.localKeys.state);
const lr = localStorage.getItem(CONFIG.localKeys.lastRead);
if (lr) lastRead = safeDate(lr);
let state = CONFIG.initialCollapsed ? 'collapsed' : (s||'expanded');
if (s === 'closed') state = 'collapsed';
frame.setAttribute('data-state', state);
updateAriaExpanded(state === 'expanded');
}catch(e){}
}
function saveLastRead(){ try{ localStorage.setItem(CONFIG.localKeys.lastRead, new Date().toISOString()); }catch(e){} }
function saveState(s){ try{ localStorage.setItem(CONFIG.localKeys.state, s); }catch(e){} }
// UI helpers
function updateAriaExpanded(isExpanded){
const state = isExpanded ? 'true' : 'false';
toggle.setAttribute('aria-expanded', state);
collapseBtn.setAttribute('aria-expanded', state);
shell.setAttribute('aria-hidden', isExpanded ? 'false' : 'true');
}
function expand(){
if(frame.getAttribute('data-state') === 'expanded') return;
frame.setAttribute('data-state','expanded');
lastRead = new Date();
saveLastRead();
updateBadge();
render(filterInput.value);
saveState('expanded');
updateAriaExpanded(true);
// Fokuskan input filter untuk pengalaman seluler yang lebih cepat
setTimeout(() => filterInput.focus(), 250);
}
function collapse(){
if(frame.getAttribute('data-state') === 'collapsed') return;
frame.setAttribute('data-state','collapsed');
closePreview();
saveState('collapsed');
updateAriaExpanded(false);
}
function close(){
frame.setAttribute('aria-hidden','true');
saveState('closed');
stopPolling();
}
// polling with visibility pause
function startPolling(){
stopPolling();
pollTimer = setInterval(()=> {
if (document.hidden) return;
fetchAndUpdate(true).catch(()=>{});
}, CONFIG.pollIntervalMs);
}
function stopPolling(){
if (pollTimer){ clearInterval(pollTimer); pollTimer = null; }
if(activeJsonp) cleanupJsonp(activeJsonp.cbName, activeJsonp.script);
}
// fetch and update
async function fetchAndUpdate(isPoll=false){
statusEl.textContent = 'Mencari...';
try{
const json = await jsonpFetch(CONFIG.feedUrl);
const items = parseBlogger(json);
if (items.length){
ARTICLES = items.map(it=>({...it, date: safeDate(it.date)}));
render(filterInput.value);
updateBadge();
statusEl.textContent = 'Online';
return;
}
}catch(e){
statusEl.textContent = 'Offline / Feed error';
}
if (!ARTICLES.length && !isPoll) {
ARTICLES = [
{id:'f1',title:'Nusantara',excerpt:'Baca artikel menarik seputar Nusantara.',url:'/p/nusantara.html',date:safeDate('2025-12-10T10:00:00Z')},
{id:'f2',title:'Teknologi Canggih',excerpt:'Update informasi terkait Teknologi terkini.',url:'/p/teknologi.html',date:safeDate('2025-12-12T12:00:00Z')}
];
}
render(filterInput.value);
updateBadge();
}
// filter debounce
function debounce(func, delay){
let timeoutId;
return function(...args){
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func.apply(this, args), delay);
};
}
// init
function boot(){
if (!frame) return;
loadState();
// greeting
const h = new Date().getHours();
let g='Halo';
if (h>=4 && h<11) g='Selamat pagi';
else if (h<15) g='Selamat siang';
else if (h<18) g='Selamat sore';
else g='Selamat malam';
welcomeEl.textContent = g + '!';
// event delegation
shadow.addEventListener('click', (ev)=>{
const t = ev.target;
// Toggle
if (t === toggle || t.closest('.wf2-toggle')) {
frame.getAttribute('data-state') === 'collapsed' ? expand() : collapse();
}
// Actions
const act = t.closest('[data-action]');
if (act){
const a = act.dataset.action;
if (a==='collapse') collapse();
else if (a==='close') close();
else if (a==='top') {
window.scrollTo({top:0,behavior:'smooth'});
// Tutup widget setelah men-scroll ke atas di mobile
if (window.innerWidth <= 720) collapse();
}
}
// Article Click
const article = t.closest('.wf2-article');
if (article){
const item = ARTICLES.find(a => a.url === article.dataset.url);
if (item) openPreview(item);
}
// Preview Close
if (t.id === 'wf2PreviewClose') closePreview();
});
// keyboard support for Enter/Space on article & Escape
shadow.addEventListener('keydown', (e)=>{
const t = e.target;
if ((e.key==='Enter' || e.key===' ') && t.classList.contains('wf2-article')){
e.preventDefault();
t.click();
}
if (e.key==='Escape'){
if (preview.getAttribute('aria-hidden') === 'false') closePreview();
else if (frame.getAttribute('data-state') === 'expanded') collapse();
}
});
// filter debounce
filterInput.addEventListener('input', debounce(()=> render(filterInput.value), 200));
// visibility change -> pause polling
document.addEventListener('visibilitychange', ()=>{ if (document.hidden) stopPolling(); else startPolling(); });
// initial fetch
fetchAndUpdate().then(()=> startPolling()).catch(()=> startPolling());
}
// start
boot();
// exposed minimal API (on host element only)
rootHost.WF2 = { refresh: ()=> fetchAndUpdate(), open: expand, close: collapse, getArticles: ()=> ARTICLES.slice() };
})();
' + esc(a.title) + '
' + esc(a.excerpt) + '
