RAD PROTOTYPE Web-App: Post-Video & Comments [source-code]

<!-- FULL REWRITTEN HTML + JS PAGE WITH 'By:' REMOVED AND ALWAYS INTERNAL NAME = MANU -->

<!doctype html>

<html lang="id">

<head>

  <meta charset="utf-8" />

  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <title>Video List — JP Kanji Ultra Translation</title>


  <style>

  /* ===== GENERAL STYLES ===== */

  .video-item{

    margin:30px 0;

    position:relative;

    background:#fcfcfc;

    border:1px solid #ddd;

    padding:15px;

    border-radius:12px;

  }

  .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;

    font-style:italic;

  }


  .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;

    white-space:pre-wrap;

  }


  /* Admin Panels */

  #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:#fcfcfc;

    border:1px solid #ddd;

    padding:10px;

    margin-top:10px;

    font-size:0.9rem;

  }


  /* 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;

  }


  .hide-video-btn{

    width:100%;

    padding:8px;

    background:#ddd;

    border:1px solid #aaa;

    border-radius:6px;

    cursor:pointer;

    margin-bottom:10px;

  }


  .video-item hr{

    border:none;

    height:0.2cm;

    background:dodgerblue;

    margin:25px 0;

    border-radius:4px;

  }


  .comment-disabled{

    background:#e9ecef !important;

    opacity:0.65;

    cursor:not-allowed !important;

  }


  .comment-disabled-banner{

    display:none;

    background:#ffdddd;

    border:1px solid #ffaaaa;

    padding:6px 10px;

    margin-bottom:10px;

    border-radius:6px;

    color:#a33;

    font-size:0.9rem;

    font-weight:bold;

    text-align:center;

  }


  .timestamp{

    font-size:0.78rem;

    color:#666;

    margin-top:6px;

    display:block;

  }

  </style>

</head>


<body>


<!-- ========= LOGIN ========= -->

<div id="login-panel">

  <h3><b>CONTROL SECTION</b> — Login (Admin)</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>


<!-- ========= ADMIN PANEL ========== -->

<div id="admin-panel">

  <h3><b>CONTROL SECTION</b> — Admin Panel</h3>

  <label><input name="comm" type="radio" value="enable" /> Enable Comments</label><br />

  <label><input name="comm" type="radio" value="disable" /> Disable Comments</label>

  <br /><br />

  <button id="logout-btn">Logout</button>

  <h4>Activity Log</h4>

  <div id="log-box"></div>

</div>


<!-- ============ VIDEO LIST ============ -->

<div id="video-list">


  <!-- VIDEO ITEMS COPY FROM ORIGINAL (SHORTENED HERE FOR EXAMPLE) -->

  <div class="video-item" data-id="1" data-src="https://youtube.com/shorts/9WVxOv5J2m0" data-title="「Siだ」ワンダーシビック Siフルバケ二脚ガチ仕様2シーター">

    <hr />

    <button class="hide-video-btn">Show Video</button>

    <iframe allowfullscreen frameborder="0" height="360" src="https://www.youtube.com/embed/9WVxOv5J2m0" width="100%" style="display:none;"></iframe>

    <div class="auto-meta"></div>


    <div class="comment-section">

      <div class="comment-disabled-banner">COMMENTS DISABLED BY ADMIN</div>

      <h4>Leave a Comment:</h4>

      <textarea class="comment-input" id="com-input-1"></textarea>


      <label class="add-extra-label"><input class="add-extra-checkbox" type="checkbox"> Additional Comment</label>

      <div class="add-extra-box"><textarea class="add-extra-textarea"></textarea></div>


      <hr style="background:#666;border:none;height:0.05cm;margin:10px 0;border-radius:2px;" />

      <div class="comment-output" id="com-output-1"></div>

    </div>

    <hr />

  </div>


  <!-- VIDEO 2 -->

  <div class="video-item" data-id="3" data-src="https://youtube.com/shorts/-w182IGAt84" data-title="">

    <hr />

    <button class="hide-video-btn">Show Video</button>

    <iframe allowfullscreen frameborder="0" height="360" src="https://www.youtube.com/embed/-w182IGAt84" width="100%" style="display:none;"></iframe>

    <div class="auto-meta"></div>

    <div class="comment-section">

      <div class="comment-disabled-banner">COMMENTS DISABLED BY ADMIN</div>

      <h4>Leave a Comment:</h4>

      <textarea class="comment-input" id="com-input-3"></textarea>

      <label class="add-extra-label"><input class="add-extra-checkbox" type="checkbox"> Additional Comment</label>

      <div class="add-extra-box"><textarea class="add-extra-textarea"></textarea></div>

      <hr style="background:#666;border:none;height:0.05cm;margin:10px 0;border-radius:2px;" />

      <div class="comment-output" id="com-output-3"></div>

    </div>

    <hr />

  </div>


  <!-- VIDEO 3 -->

  <div class="video-item" data-id="16" data-src="https://youtube.com/shorts/F2j9mWgxV0o" data-title="">

    <hr />

    <button class="hide-video-btn">Show Video</button>

    <iframe allowfullscreen frameborder="0" height="360" src="https://www.youtube.com/embed/F2j9mWgxV0o" width="100%" style="display:none;"></iframe>

    <div class="auto-meta"></div>

    <div class="comment-section">

      <div class="comment-disabled-banner">COMMENTS DISABLED BY ADMIN</div>

      <h4>Leave a Comment:</h4>

      <textarea class="comment-input" id="com-input-16"></textarea>

      <label class="add-extra-label"><input class="add-extra-checkbox" type="checkbox"> Additional Comment</label>

      <div class="add-extra-box"><textarea class="add-extra-textarea"></textarea></div>

      <hr style="background:#666;border:none;height:0.05cm;margin:10px 0;border-radius:2px;" />

      <div class="comment-output" id="com-output-16"></div>

    </div>

    <hr />

  </div>


  <!-- VIDEO 4 -->

  <div class="video-item" data-id="22" data-src="https://youtube.com/shorts/oqtJYTjAnnE" data-title="">

    <hr />

    <button class="hide-video-btn">Show Video</button>

    <iframe allowfullscreen frameborder="0" height="360" src="https://www.youtube.com/embed/oqtJYTjAnnE" width="100%" style="display:none;"></iframe>

    <div class="auto-meta"></div>

    <div class="comment-section">

      <div class="comment-disabled-banner">COMMENTS DISABLED BY ADMIN</div>

      <h4>Leave a Comment:</h4>

      <textarea class="comment-input" id="com-input-22"></textarea>

      <label class="add-extra-label"><input class="add-extra-checkbox" type="checkbox"> Additional Comment</label>

      <div class="add-extra-box"><textarea class="add-extra-textarea"></textarea></div>

      <hr style="background:#666;border:none;height:0.05cm;margin:10px 0;border-radius:2px;" />

      <div class="comment-output" id="com-output-22"></div>

    </div>

    <hr


</div><!-- end video list -->


<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(){


  function escapeHtml(s){return String(s||"").replace(/[&<>"]/g,m=>({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;"}[m]));}

  function fmtTime(ts){return ts?new Date(ts).toLocaleString():"";}


  /* ===== FIREBASE INIT ===== */

  firebase.initializeApp({

    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"

  });

  const rdb = firebase.database();


  window.currentCommentState = "enable";


  /* ALWAYS RETURN MANU (internally only) */

  function ensureVisitorName(){ return "MANU"; }


  /* write logs */

  function writeAdminAction(msg){rdb.ref("logs/admin_actions").push().set({message:msg,timestamp:firebase.database.ServerValue.TIMESTAMP});}


  /* LOGIN */

  document.getElementById("login-btn").onclick=function(){

    const u=document.getElementById("user").value.trim();

    const p=document.getElementById("pass").value;

    if(u==="manu"&&p==="manu"){sessionStorage.setItem("manu_login","true");location.reload();}

    else alert("Wrong login");

  };

  document.getElementById("logout-btn").onclick=function(){sessionStorage.setItem("manu_login","false");location.reload();};


  if(sessionStorage.getItem("manu_login")==="true"){

    document.getElementById("login-panel").style.display="none";

    document.getElementById("admin-panel").style.display="block";


    rdb.ref("config/comment_state").once("value",snap=>{

      const cur=snap.val()||"enable";

      document.querySelectorAll("input[name='comm']").forEach(r=>{r.checked=(r.value===cur);});

      updateCommentBoxesState();

    });


    const lb=document.getElementById("log-box");

    lb.innerHTML="<i>Loading...</i>";

    rdb.ref("logs/admin_actions").orderByChild("timestamp").on("child_added",snap=>{

      if(lb.innerHTML.includes("Loading")) lb.innerHTML="";

      const v=snap.val();

      if(v){

        const d=document.createElement("div");

        d.textContent=fmtTime(v.timestamp)+" — "+v.message;

        lb.prepend(d);

      }

    });

  }


  document.querySelectorAll("input[name='comm']").forEach(r=>{

    r.addEventListener("change",function(){

      rdb.ref("config/comment_state").set(this.value);

      writeAdminAction("Comment state -> "+this.value);

    });

  });


  /* UPDATE COMMENT ENABLE/DISABLE */

  function updateCommentBoxesState(){

    const disabled=(window.currentCommentState==="disable");

    document.querySelectorAll(".comment-disabled-banner").forEach(b=>b.style.display=disabled?"block":"none");

    document.querySelectorAll(".comment-input,.add-extra-textarea").forEach(b=>{b.disabled=disabled;b.classList.toggle("comment-disabled",disabled);});

    document.querySelectorAll(".add-extra-checkbox").forEach(cb=>{cb.disabled=disabled;cb.style.cursor=disabled?"not-allowed":"pointer";});

  }


  rdb.ref("config/comment_state").on("value",snap=>{

    window.currentCommentState=snap.val()||"enable";

    updateCommentBoxesState();

  });


  /* COMMENT WRITE */

  const writeTimers={};

  function writeCommentDebounced(id,obj){

    if(writeTimers[id]) clearTimeout(writeTimers[id]);

    writeTimers[id]=setTimeout(()=>{

      obj.updated=Date.now();

      rdb.ref("comments/"+id).update(obj);

    },300);

  }


  /* COMMENT BINDINGS */

  document.querySelectorAll(".video-item").forEach(vi=>{

    const id=vi.dataset.id;

    const ta=vi.querySelector(".comment-input");

    const extraTa=vi.querySelector(".add-extra-textarea");

    const extraCb=vi.querySelector(".add-extra-checkbox");

    const extraWrap=vi.querySelector(".add-extra-box");

    const out=vi.querySelector(".comment-output");


    if(ta){

      ta.addEventListener("input",()=>{

        if(window.currentCommentState==="disable") return;

        writeCommentDebounced(id,{main:ta.value.trim(),by:ensureVisitorName()});

      });

    }


    if(extraTa){

      extraTa.addEventListener("input",()=>{

        if(window.currentCommentState==="disable") return;

        writeCommentDebounced(id,{extra:extraTa.value.trim(),by:ensureVisitorName()});

      });

    }


    if(extraCb){

      extraCb.addEventListener("change",()=>{

        if(extraCb.checked){extraWrap.style.display="block";}

        else{

          if(extraTa.value.trim()){

            if(confirm("Delete extra comment?")){

              extraTa.value="";

              extraWrap.style.display="none";

              writeCommentDebounced(id,{extra:""});

            } else extraCb.checked=true;

          } else{

            extraWrap.style.display="none";

            writeCommentDebounced(id,{extra:""});

          }

        }

      });

    }


    /* RENDER COMMENTS (WITHOUT "BY:") */

    rdb.ref("comments/"+id).on("value",snap=>{

      const v=snap.val()||{};

      let html="";

      if(v.main) html += `<div><b>Comment:</b><div>${escapeHtml(v.main)}</div></div>`;

      if(v.extra) html += `<div style='margin-top:8px'><b>Extra:</b><div>${escapeHtml(v.extra)}</div></div>`;

      if(!html) html = "<i>(No comments)</i>";


      let tsHTML="";

      if(v.updated){ tsHTML = `<div class='timestamp'>${fmtTime(v.updated)}</div>`; }


      out.innerHTML = html + tsHTML;


      if(ta && ta.value !== (v.main||"")) ta.value = v.main||"";


      if(extraTa){

        if(v.extra){extraCb.checked=true;extraWrap.style.display="block";extraTa.value=v.extra;}

        else{extraCb.checked=false;extraWrap.style.display="none";extraTa.value="";}

      }

    });

  });


  /* METADATA + TRANSLATION */

  document.querySelectorAll(".video-item").forEach(vi=>{

    const url=vi.dataset.src;

    const id =vi.dataset.id;

    const box=vi.querySelector(".auto-meta");

    if(!box) return;


    (async()=>{

      const fixed=url.replace("youtube.com/shorts/","youtube.com/watch?v=");

      let title="";


      try{

        const r=await fetch("https://www.youtube.com/oembed?url="+encodeURIComponent(fixed)+"&format=json");

        if(r.ok){const meta=await r.json();title=meta.title||"";}

      }catch{}


      if(!title){title=vi.getAttribute("data-title")||"(title unavailable)";}


      vi.setAttribute("data-title",title);


      box.innerHTML=`<p class='meta-title'>${escapeHtml(title)}</p><p class='meta-translation'><b>English:</b> Loading...</p>`;


      const saved = await rdb.ref("translations/"+id).once("value");

      if(saved.exists()){

        const t=saved.val().translated;

        box.querySelector(".meta-translation").innerHTML=`<b>English:</b> ${escapeHtml(t)}`;

        return;

      }


      const eng = await translateSmartSingle(title);

      rdb.ref("translations/"+id).set({original:title,translated:eng,saved:Date.now()});


      box.querySelector(".meta-translation").innerHTML = `<b>English:</b> ${escapeHtml(eng)}`;

    })();

  });


  /* DETECT KANJI */

  function containsKanji(s){return /^[\u4E00-\u9FFF]+$/.test(s)||/[\u4E00-\u9FFF]/.test(s);}


  /* SPECIAL JP TRANSLATOR */

  function translateCNMirror(text){

    return fetch("https://translate.googleapis.cn/translate_a/single?client=gtx&sl=ja&tl=en&dt=t&q="+encodeURIComponent(text))

      .then(r=>r.json()).then(arr=>arr[0].map(i=>i[0]).join(""))

      .catch(()=>"");

  }


  function detectLang(text){

    return fetch("https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q="+encodeURIComponent(text))

      .then(r=>r.json()).then(j=>j[2]).catch(()=>"auto");

  }


  /* SMART 4-LAYER PIPELINE */

  function translateSmartSingle(text){

    return new Promise(resolve=>{


      function layer0(){

        if(!containsKanji(text)) return layer1();

        translateCNMirror(text).then(res=>{res?resolve(res):layer1();});

      }


      function layer1(){

        fetch("https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=en&dt=t&q="+encodeURIComponent(text))

        .then(r=>r.json()).then(arr=>{

          const out=arr[0].map(a=>a[0]).join("").trim();

          out?resolve(out):layer2();

        }).catch(()=>layer2());

      }


      function layer2(){

        detectLang(text).then(src=>{

          if(src==="auto") src="ja";

          fetch(`https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=${src}|en`)

          .then(r=>r.json()).then(j=>{

            if(j.responseData.translatedText) resolve(j.responseData.translatedText);

            else layer3();

          }).catch(()=>layer3());

        });

      }


      function layer3(){ resolve(text); }


      layer0();

    });

  }


  /* HIDE/SHOW VIDEO */

  document.querySelectorAll(".video-item").forEach(vi=>{

    const btn=vi.querySelector(".hide-video-btn");

    const f=vi.querySelector("iframe");

    f.style.display="none";

    btn.textContent="Show Video";

    btn.onclick=()=>{

      const h=(f.style.display==="none");

      f.style.display=h?"":"none";

      btn.textContent=h?"Hide Video":"Show Video";

    };

  });


  updateCommentBoxesState();


});

</script>

</body>

</html>


Comments