<!-- Paste this entire block into Blogger post (HTML mode) -->
<style>
/* Styles Part 1 (main) */
.video-item { margin-bottom: 18px; }
.meta-title { margin-top:8px; font-style:italic; font-size:1.05rem; font-weight:bold; }
.meta-translation { color:#444; margin-bottom:6px; font-size:0.95rem; }
.comment-section { margin-top:10px; }
.comment-input { width:100%; min-height:65px; }
.comment-output { margin-top:8px; background:#f5f5f5; padding:6px; min-height:22px; border-radius:4px; }
/* Control Section */
#login-panel, #admin-panel {
border: 2px solid #666; padding: 20px; border-radius: 10px;
background: #f0f0f0; margin-bottom: 25px;
}
#admin-panel { display:none; }
#log-box {
background:#fff; border:1px solid #aaa; padding:10px;
max-height:200px; overflow-y:auto; margin-top:10px;
}
/* Additional comment */
.add-extra-label { margin-top:6px; display:block; cursor:pointer; }
.add-extra-box { display:none; width:100%; margin-top:6px; }
.add-extra-textarea { width:100%; resize:vertical; min-height:120px; }
/* balloon confirm */
.ballon-confirm {
position:absolute;
background:#fff;
border:1px solid #888;
padding:10px;
border-radius:6px;
box-shadow:0 2px 6px rgba(0,0,0,0.2);
z-index:9999;
max-width:300px;
font-size:0.95rem;
}
/* Styles Part 2 (summary) */
#summary-sep { text-align:center; margin-top:40px; margin-bottom:10px; font-size:1.4rem; font-weight:bold; }
.sum-video-item { margin-bottom: 22px; }
.sum-comment-box {
background:#f7f7f7; border:1px solid #aaa; padding:10px; border-radius:6px;
min-height:40px; font-size:0.95rem;
}
.sum-extra-box {
background:#fff; border:1px dashed #ccc; padding:8px; border-radius:4px; margin-top:8px;
font-size:0.95rem;
}
</style>
<!--===========================
CONTROL SECTION (LOGIN / ADMIN)
===========================-->
<div id="login-panel">
<h3><b>CONTROL SECTION</b> — Login</h3>
<label>Username:</label><br />
<input id="user" type="text" /><br /><br />
<label>Password:</label><br />
<input id="pass" type="password" /><br /><br />
<button id="login-btn">Login</button>
</div>
<div id="admin-panel">
<h3><b>CONTROL SECTION</b> — Admin Panel</h3>
<label><input name="comm" type="radio" value="enable" /> Enable All Comments</label><br />
<label><input name="comm" type="radio" value="disable" /> Disable All Comments</label>
<br /><br />
<button id="logout-btn">Logout</button>
<h4>Activity Log</h4>
<div id="log-box"></div>
</div>
<!--===========================
PART 1 — MAIN VIDEO LIST (editable comments)
===========================-->
<div id="video-list">
<!--ITEM 1-->
<div class="video-item" data-id="1" data-src="https://youtube.com/shorts/9WVxOv5J2m0">
<br /><br />
<iframe allowfullscreen frameborder="0" height="360" src="https://www.youtube.com/embed/9WVxOv5J2m0" width="100%"></iframe>
<div class="auto-meta" data-id="meta-1"></div>
<div class="comment-section">
<h4>Leave a Comment:</h4>
<textarea class="comment-input" id="com-input-1" placeholder="Type your comment..."></textarea><br />
<label class="add-extra-label">
<input type="checkbox" class="add-extra-checkbox"> Additional Comment
</label>
<div class="add-extra-box">
<textarea rows="4" class="add-extra-textarea" placeholder="Additional detail..."></textarea>
</div>
<div class="comment-output" id="com-output-1"></div>
</div>
<hr />
</div>
<!--ITEM 2-->
<div class="video-item" data-id="3" data-src="https://youtube.com/shorts/-w182IGAt84">
<br /><br />
<iframe allowfullscreen frameborder="0" height="360" src="https://www.youtube.com/embed/-w182IGAt84" width="100%"></iframe>
<div class="auto-meta" data-id="meta-3"></div>
<div class="comment-section">
<h4>Leave a Comment:</h4>
<textarea class="comment-input" id="com-input-3" placeholder="Type your comment..."></textarea><br />
<label class="add-extra-label">
<input type="checkbox" class="add-extra-checkbox"> Additional Comment
</label>
<div class="add-extra-box">
<textarea rows="4" class="add-extra-textarea" placeholder="Additional detail..."></textarea>
</div>
<div class="comment-output" id="com-output-3"></div>
</div>
<hr />
</div>
<!--ITEM 3-->
<div class="video-item" data-id="16" data-src="https://youtube.com/shorts/F2j9mWgxV0o">
<br /><br />
<iframe allowfullscreen frameborder="0" height="360" src="https://www.youtube.com/embed/F2j9mWgxV0o" width="100%"></iframe>
<div class="auto-meta" data-id="meta-16"></div>
<div class="comment-section">
<h4>Leave a Comment:</h4>
<textarea class="comment-input" id="com-input-16" placeholder="Type your comment..."></textarea><br />
<label class="add-extra-label">
<input type="checkbox" class="add-extra-checkbox"> Additional Comment
</label>
<div class="add-extra-box">
<textarea rows="4" class="add-extra-textarea" placeholder="Additional detail..."></textarea>
</div>
<div class="comment-output" id="com-output-16"></div>
</div>
<hr />
</div>
<!--ITEM 4-->
<div class="video-item" data-id="22" data-src="https://youtube.com/shorts/oqtJYTjAnnE">
<br /><br />
<iframe allowfullscreen frameborder="0" height="360" src="https://www.youtube.com/embed/oqtJYTjAnnE" width="100%"></iframe>
<div class="auto-meta" data-id="meta-22"></div>
<div class="comment-section">
<h4>Leave a Comment:</h4>
<textarea class="comment-input" id="com-input-22" placeholder="Type your comment..."></textarea><br />
<label class="add-extra-label">
<input type="checkbox" class="add-extra-checkbox"> Additional Comment
</label>
<div class="add-extra-box">
<textarea rows="4" class="add-extra-textarea" placeholder="Additional detail..."></textarea>
</div>
<div class="comment-output" id="com-output-22"></div>
</div>
<hr />
</div>
</div>
<!--===========================
PART 2 — SUMMARY (read-only duplicate of Part1 comments)
===========================-->
<div id="summary-sep">SUMMARY</div>
<p style="font-style: italic; margin-bottom: 20px; text-align: center;">
Ringkasan komentar (read-only) — menampilkan isi komentar yang telah disimpan di Part 1.
</p>
<div id="summary-video-list">
<div class="sum-video-item" data-id="1">
<iframe allowfullscreen frameborder="0" height="240" src="https://www.youtube.com/embed/9WVxOv5J2m0" width="100%"></iframe>
<div class="sum-meta" data-id="sum-meta-1"></div>
<h4>Comment (Read Only):</h4>
<div class="sum-comment-box" id="sum-com-1"></div>
<div class="sum-extra-box" id="sum-extra-1"></div>
<hr />
</div>
<div class="sum-video-item" data-id="3">
<iframe allowfullscreen frameborder="0" height="240" src="https://www.youtube.com/embed/-w182IGAt84" width="100%"></iframe>
<div class="sum-meta" data-id="sum-meta-3"></div>
<h4>Comment (Read Only):</h4>
<div class="sum-comment-box" id="sum-com-3"></div>
<div class="sum-extra-box" id="sum-extra-3"></div>
<hr />
</div>
<div class="sum-video-item" data-id="16">
<iframe allowfullscreen frameborder="0" height="240" src="https://www.youtube.com/embed/F2j9mWgxV0o" width="100%"></iframe>
<div class="sum-meta" data-id="sum-meta-16"></div>
<h4>Comment (Read Only):</h4>
<div class="sum-comment-box" id="sum-com-16"></div>
<div class="sum-extra-box" id="sum-extra-16"></div>
<hr />
</div>
<div class="sum-video-item" data-id="22">
<iframe allowfullscreen frameborder="0" height="240" src="https://www.youtube.com/embed/oqtJYTjAnnE" width="100%"></iframe>
<div class="sum-meta" data-id="sum-meta-22"></div>
<h4>Comment (Read Only):</h4>
<div class="sum-comment-box" id="sum-com-22"></div>
<div class="sum-extra-box" id="sum-extra-22"></div>
<hr />
</div>
</div>
<!--Firebase SDK (compat)-->
<script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-app-compat.js"></script>
<script src="https://www.gstatic.com/firebasejs/9.22.2/firebase-database-compat.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function(){
/* ---------- helpers ---------- */
function escapeHtml(s){
return String(s || "").replace(/[&<>"']/g,
m=>({"&":"&","<":"<",">":">","\"":""","'":"'"}[m]));
}
function onClick(id, handler){
const el = document.getElementById(id);
if (el) el.addEventListener("click", handler);
}
/* ---------- Firebase config (INSERTED) ---------- */
const firebaseConfig = {
apiKey: "AIzaSyAF5r6Rvu-DmoV-vf47wYTZfarpVGmNYR0",
authDomain: "ronin-11938.firebaseapp.com",
databaseURL: "https://ronin-11938-default-rtdb.firebaseio.com",
projectId: "ronin-11938",
storageBucket: "ronin-11938.firebasestorage.app",
messagingSenderId: "823368889742",
appId: "1:823368889742:web:609e16ace214b94a0df",
measurementId: "G-H2H8GSMQ94"
};
// init firebase
firebase.initializeApp(firebaseConfig);
const rdb = firebase.database();
/* ---------- small local state mirrors ---------- */
window.commentsDB = {}; // populated by listener
window.currentCommentState = "enable";
/* ---------- log utils (now stored in Firebase) ---------- */
function addLogFirebase(text){
const node = rdb.ref('logs').push();
node.set({ text: text, ts: Date.now() });
}
function renderLogFromFirebase(snapshot){
const val = snapshot ? (snapshot.val()||{}) : {};
const arr = Object.values(val);
arr.sort((a,b)=>b.ts - a.ts);
const b = document.getElementById("log-box");
if (b) b.innerHTML = arr.map(x=>`<div>[${new Date(x.ts).toLocaleString()}] ${escapeHtml(x.text)}</div>`).join("");
}
/* ---------- comment state (stored in Firebase) ---------- */
function applyCommentState(state){
const disabled = (state === "disable");
window.currentCommentState = state;
document.querySelectorAll(".comment-input").forEach(e=>{ e.disabled = disabled; e.readOnly = disabled; });
document.querySelectorAll(".add-extra-textarea").forEach(e=>{ e.disabled = disabled; e.readOnly = disabled; });
document.querySelectorAll(".add-extra-checkbox").forEach(e=>{ e.disabled = disabled; });
// there are no submit buttons anymore, keep logs only
}
function setCommentStateFirebase(v){
rdb.ref('config/comment_state').set(v);
addLogFirebase(v === "enable" ? "Comments ENABLED" : "Comments DISABLED");
}
/* ---------- metadata & translate ---------- */
const GOOGLE_TRANSLATE_ENDPOINT =
"https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q=";
async function translateSmartSingle(text){
if (!text) return "";
try{
let r = await fetch(GOOGLE_TRANSLATE_ENDPOINT + encodeURIComponent(text));
if (r.ok){
let arr = await r.json();
if (arr && arr[0]) {
let out = "";
for (let seg of arr[0]) if (seg[0]) out += seg[0];
if (out.trim()) return out;
}
}
}catch(e){}
return text;
}
async function loadMetadataInto(box, videoURL){
if (!box) return;
box.innerHTML = "<i>Loading...</i>";
try{
let r = await fetch("https://www.youtube.com/oembed?url="+encodeURIComponent(videoURL)+"&format=json");
if (!r.ok) throw 0;
let meta = await r.json();
let title = meta.title || "(no title)";
box.innerHTML = `<p class="meta-title">${escapeHtml(title)}</p><p class="meta-translation"><b>English:</b> Translating...</p>`;
let eng = await translateSmartSingle(title);
const transEl = box.querySelector(".meta-translation");
if (transEl) transEl.innerHTML = `<b>English:</b> ${escapeHtml(eng)}`;
}catch(e){
box.innerHTML = "<p>(Metadata unavailable)</p>";
}
}
/* ---------- helpers for extra comment saving/deleting & balloon ---------- */
function showBalloonConfirm(target, text, onYes, onNo){
// remove existing balloons
document.querySelectorAll('.ballon-confirm').forEach(e=>e.remove());
const box = document.createElement("div");
box.className = "ballon-confirm";
box.innerHTML = `
<div style="margin-bottom:8px">${escapeHtml(text)}</div>
<div style="text-align:right">
<button class="bc-yes">Yes</button>
<button class="bc-no">No</button>
</div>
`;
document.body.appendChild(box);
const r = target.getBoundingClientRect();
// position: prefer below, fallback to above if not enough space
const topBelow = r.bottom + 6;
const topAbove = r.top - box.offsetHeight - 6;
box.style.left = Math.max(8, r.left) + "px";
box.style.top = topBelow + "px";
box.querySelector(".bc-yes").onclick = ()=>{ box.remove(); onYes(); };
box.querySelector(".bc-no").onclick = ()=>{ box.remove(); onNo(); };
// remove if click outside
function outside(e){
if (!box.contains(e.target) && e.target !== target) { box.remove(); document.removeEventListener('click', outside); }
}
setTimeout(()=>document.addEventListener('click', outside), 10);
}
function updateMainComment(id, val){
rdb.ref('comments/'+id+'/text').set(val || "");
rdb.ref('comments/'+id).update({ updatedAt: Date.now() });
}
function updateExtraComment(id, val){
if (val === null) {
rdb.ref('comments/'+id+'/text-area').remove();
} else {
rdb.ref('comments/'+id+'/text-area').set(val || "");
rdb.ref('comments/'+id).update({ updatedAt: Date.now() });
}
}
/* ---------- Firebase listeners to keep UI in sync ---------- */
rdb.ref('comments').on('value', function(snapshot){
const val = snapshot.val() || {};
window.commentsDB = val;
// render comments into Part1 and Part2
document.querySelectorAll(".video-item").forEach(vi=>{
const id = vi.getAttribute("data-id");
if (!id) return;
const ta = vi.querySelector(".comment-input");
const out = vi.querySelector(".comment-output");
const extraWrap = vi.querySelector(".add-extra-box");
const extraTa = vi.querySelector(".add-extra-textarea");
const extraCb = vi.querySelector(".add-extra-checkbox");
const entry = val[id] || {};
const text = entry.text || "";
const extra = entry["text-area"] || "";
if (ta && ta !== document.activeElement) ta.value = text; // don't overwrite when typing
if (out) out.innerHTML = text ? `<p>${escapeHtml(text)}</p>` : "";
// extra
if (extraTa && extraCb && extraWrap){
if (extra){
extraCb.checked = true;
extraWrap.style.display = "block";
if (extraTa !== document.activeElement) extraTa.value = extra;
} else {
// if there's no stored extra, keep checkbox state if user currently typing; otherwise reset
if (extraTa !== document.activeElement) extraTa.value = "";
if (!extraTa || !extraTa.value) {
extraCb.checked = false;
extraWrap.style.display = "none";
}
}
}
});
// summary: main and extra
["1","3","16","22"].forEach(id => {
const b = document.getElementById("sum-com-" + id);
const e = document.getElementById("sum-extra-" + id);
const item = val[id] || {};
if (b) b.innerHTML = item.text ? escapeHtml(item.text) : "<i>(No comment entered)</i>";
if (e) e.innerHTML = item["text-area"] ? escapeHtml(item["text-area"]) : "";
});
});
// comment_state listener
rdb.ref('config/comment_state').on('value', function(snapshot){
const state = snapshot.val() || "enable";
// apply UI
applyCommentState(state);
const sel = document.querySelector(`input[name='comm'][value='${state}']`);
if (sel) sel.checked = true;
});
// logs listener
rdb.ref('logs').on('value', function(snapshot){
renderLogFromFirebase(snapshot);
});
/* ---------- admin login UI (keeps same username/password UX as before) ---------- */
const loginPanel = document.getElementById("login-panel");
const adminPanel = document.getElementById("admin-panel");
// preserve session per tab (not persistent cross-browser)
function isLoggedIn(){
return sessionStorage.getItem("manu_login") === "true";
}
if (isLoggedIn()){
if (loginPanel) loginPanel.style.display = "none";
if (adminPanel) adminPanel.style.display = "block";
addLogFirebase("Auto login (session)");
}
onClick("login-btn", function(){
const u = (document.getElementById("user")||{}).value || "";
const p = (document.getElementById("pass")||{}).value || "";
if (u === "manu" && p === "manu"){
sessionStorage.setItem("manu_login","true");
if (loginPanel) loginPanel.style.display = "none";
if (adminPanel) adminPanel.style.display = "block";
addLogFirebase("User manu logged in");
} else {
alert("Wrong username or password.");
}
});
onClick("logout-btn", function(){
addLogFirebase("User manu logged out");
sessionStorage.setItem("manu_login","false");
location.reload();
});
// radio change -> update Firebase config
document.querySelectorAll("input[name='comm']").forEach(r=>{
r.addEventListener("change", function(){
const v = this.value;
setCommentStateFirebase(v);
applyCommentState(v);
});
});
/* ---------- initialize metadata for both parts ---------- */
document.querySelectorAll(".video-item").forEach(vi=>{
const url = vi.getAttribute("data-src");
const metaBox = vi.querySelector(".auto-meta");
if (metaBox && url) loadMetadataInto(metaBox, url);
});
// summary meta boxes
const idToUrl = {};
document.querySelectorAll(".video-item").forEach(vi=>{
const id = vi.getAttribute("data-id");
const url = vi.getAttribute("data-src");
if (id) idToUrl[id] = url;
});
document.querySelectorAll("[data-id^='sum-meta-'], .sum-meta").forEach(el=>{
const parent = el.closest(".sum-video-item");
if (!parent) return;
const id = parent.getAttribute("data-id");
const url = idToUrl[id];
if (url) loadMetadataInto(el, url);
});
/* ---------- attach auto-save handlers that write to Firebase ---------- */
document.querySelectorAll(".video-item").forEach(vi=>{
const id = vi.getAttribute("data-id");
if (!id) return;
const ta = vi.querySelector(".comment-input");
const extraWrap = vi.querySelector(".add-extra-box");
const extraTa = vi.querySelector(".add-extra-textarea");
const extraCb = vi.querySelector(".add-extra-checkbox");
const out = vi.querySelector(".comment-output");
// main textarea auto-save on input (debounce small)
let mainTimer = null;
if (ta){
ta.addEventListener("input", function(){
if (window.currentCommentState === "disable") return;
clearTimeout(mainTimer);
mainTimer = setTimeout(()=> updateMainComment(id, ta.value.trim()), 450);
});
}
// extra textarea auto-save
let extraTimer = null;
if (extraTa){
extraTa.addEventListener("input", function(){
if (window.currentCommentState === "disable") return;
clearTimeout(extraTimer);
extraTimer = setTimeout(()=> updateExtraComment(id, extraTa.value.trim()), 450);
});
}
// checkbox behaviour: show/hide extra box; on uncheck with content => confirm balloon and delete on yes
if (extraCb){
extraCb.addEventListener("change", function(){
if (window.currentCommentState === "disable"){
// prevent toggling while disabled
extraCb.checked = !extraCb.checked;
return;
}
if (extraCb.checked){
if (extraWrap) extraWrap.style.display = "block";
} else {
const currentVal = (extraTa && extraTa.value) ? extraTa.value.trim() : "";
if (currentVal){
showBalloonConfirm(extraCb, "Remove additional comment? This will DELETE saved additional comment.", function(){
// yes
if (extraWrap) extraWrap.style.display = "none";
if (extraTa) extraTa.value = "";
updateExtraComment(id, null); // remove from firebase
}, function(){
// no
extraCb.checked = true;
if (extraWrap) extraWrap.style.display = "block";
});
} else {
// no value, just hide and ensure deleted
if (extraWrap) extraWrap.style.display = "none";
updateExtraComment(id, null);
}
}
});
}
});
// ensure summary boxes show something on load if DB empty
["1","3","16","22"].forEach(id => {
const b = document.getElementById("sum-com-" + id);
if (b && (!b.innerHTML || b.innerHTML.trim()==="")) b.innerHTML = "<i>(No comment entered)</i>";
});
}); // end DOMContentLoaded
</script>
Comments
Post a Comment