poke/html/account-me.ejs
2025-10-18 22:40:02 +02:00

445 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>My Poke | Subscriptions</title>
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="librejs-license" content="GPL-3.0-or-later">
<link href="/css/yt-ukraine.svg?v=6" rel="icon">
<link href="/css/app.main.css?v=44600" rel="stylesheet">
<style>
:root {
--bg1:#10081e; --bg2:#1a0b28; --bg3:#06131a; --bg4:#0f132b;
--glass:rgba(255,255,255,0.06);
--border:rgba(255,255,255,0.1);
--accent:#66ccff;
--muted:#c9c9c9;
--ink:#fff;
--blur:24px;
--radius:18px;
--shadow:0 4px 30px rgba(0,0,0,0.3);
--hover-glow:0 0 8px rgba(102,204,255,0.35);
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;height:100%}
body{
font-family:system-ui,-apple-system,"Segoe UI",Roboto,Ubuntu;
color:var(--ink);
background:radial-gradient(circle at 20% 20%,var(--bg1),var(--bg2),var(--bg3),var(--bg4));
background-size:400% 400%;
animation:bg-pan 60s ease infinite;
overflow:hidden;
}
@keyframes bg-pan{
0%{background-position:0 50%}
50%{background-position:100% 50%}
100%{background-position:0 50%}
}
a{color:inherit;text-decoration:none !important;outline:none}
.layout{
display:grid;
grid-template-columns:320px 1fr;
height:100vh;
}
@media(max-width:780px){
.layout{grid-template-columns:1fr}
aside{height:45vh}
main{height:55vh}
}
aside{
background:var(--glass);
backdrop-filter:blur(var(--blur)) saturate(180%);
-webkit-backdrop-filter:blur(var(--blur)) saturate(180%);
border-right:1px solid var(--border);
display:flex;
flex-direction:column;
justify-content:space-between;
box-shadow:var(--shadow);
}
header{
padding:16px;
border-bottom:1px solid var(--border);
display:flex;
flex-direction:column;
gap:4px;
}
h1{
font-family:"poketube flex",system-ui;
font-size:1.6rem;
font-weight:800;
background:linear-gradient(90deg,#9fe6ff,#66ccff 70%);
-webkit-background-clip:text;
-webkit-text-fill-color:transparent;
margin:0;
}
.search-bar{
display:flex;
align-items:center;
gap:8px;
background:rgba(255,255,255,0.07);
border:1px solid var(--border);
border-radius:12px;
margin:14px 12px;
padding:6px 10px;
transition:border .2s ease;
}
.search-bar:focus-within{
border-color:var(--accent);
box-shadow:var(--hover-glow);
}
.search-bar input{
all:unset;
flex:1;
font-size:14px;
}
.search-bar button{
all:unset;
cursor:pointer;
font-size:12px;
padding:4px 8px;
border-radius:8px;
background:rgba(255,255,255,0.1);
border:1px solid var(--border);
transition:background .2s ease;
}
.search-bar button:hover{background:rgba(255,255,255,0.25)}
.sort{
display:flex;
justify-content:center;
gap:8px;
margin:0 12px 10px;
}
.sort button{
all:unset;
padding:6px 10px;
border-radius:8px;
background:rgba(255,255,255,0.08);
border:1px solid var(--border);
font-size:13px;
cursor:pointer;
transition:background .2s ease, box-shadow .2s ease;
}
.sort button:hover{background:rgba(255,255,255,0.15)}
.sort button.active{
background:rgba(255,255,255,0.25);
border-color:var(--accent);
box-shadow:var(--hover-glow);
}
.channel-list{
flex:1;
overflow-y:auto;
padding:6px 8px;
display:flex;
flex-direction:column;
gap:6px;
}
.channel{
display:flex;
align-items:center;
gap:10px;
padding:8px 10px;
border-radius:12px;
background:rgba(255,255,255,0.05);
border:1px solid transparent;
transition:all .25s ease;
}
.channel:hover{
background:rgba(255,255,255,0.15);
box-shadow:var(--hover-glow);
}
.channel.active{
border-color:var(--accent);
background:linear-gradient(135deg,rgba(102,204,255,0.18),rgba(102,204,255,0.08));
box-shadow:var(--hover-glow);
}
.channel img{
width:36px;
height:36px;
border-radius:50%;
object-fit:cover;
}
.channel-name{
flex:1;
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
font-size:14px;
}
.pill--unsub{
font-size:12px;
color:var(--ink);
background:rgba(255,255,255,0.08);
padding:4px 10px;
border-radius:999px;
border:1px solid var(--border);
transition:background .2s ease, color .2s ease;
white-space:nowrap;
}
.pill--unsub:hover{
background:rgba(255,80,80,0.2);
border-color:#ff7777;
color:#ffaaaa;
}
.sidebar-footer{
border-top:1px solid var(--border);
padding:14px;
display:flex;
flex-direction:column;
gap:8px;
background:rgba(255,255,255,0.04);
}
.uid{
font-size:12px;
color:var(--muted);
background:rgba(255,255,255,0.08);
padding:4px 10px;
border-radius:999px;
border:1px solid var(--border);
align-self:flex-start;
}
.privacy-link{
font-size:12.5px;
color:var(--accent);
text-decoration:none !important;
transition:opacity .2s ease;
}
.privacy-link:hover{opacity:0.7}
main{
position:relative;
display:flex;
flex-direction:column;
background:rgba(255,255,255,0.03);
backdrop-filter:blur(var(--blur));
-webkit-backdrop-filter:blur(var(--blur));
height:100%;
}
.toolbar{
display:flex;
align-items:center;
justify-content:space-between;
padding:12px 16px;
border-bottom:1px solid var(--border);
background:rgba(255,255,255,0.04);
z-index:5;
}
.toolbar .btn{
all:unset;
padding:6px 12px;
border-radius:8px;
background:rgba(255,255,255,0.08);
border:1px solid var(--border);
font-size:13px;
cursor:pointer;
transition:background .2s ease, transform .2s ease;
}
.toolbar .btn:hover{
background:rgba(255,255,255,0.2);
transform:translateY(-1px);
}
.viewer{
flex:1;
overflow:hidden;
position:relative;
display:flex;
align-items:center;
justify-content:center;
}
iframe{
width:100%;
height:100%;
border:none;
border-radius:0;
opacity:0;
animation:fadein .4s forwards ease;
}
@keyframes fadein{
from{opacity:0;transform:scale(0.98);}
to{opacity:1;transform:scale(1);}
}
.placeholder{
color:var(--muted);
font-size:15px;
text-align:center;
padding:20px;
}
.close-view{
position:absolute;
top:14px;
right:14px;
z-index:10;
background:rgba(255,255,255,0.08);
border:1px solid var(--border);
border-radius:50%;
width:32px;
height:32px;
display:flex;
align-items:center;
justify-content:center;
cursor:pointer;
backdrop-filter:blur(10px);
font-size:16px;
transition:background .2s ease, transform .2s ease;
}
.close-view:hover{
background:rgba(255,255,255,0.2);
transform:rotate(90deg);
}
noscript{
display:block;
background:#1a101e;
color:#fff;
text-align:center;
padding:16px;
border-top:2px solid var(--accent);
border-bottom:2px solid var(--accent);
font-size:14px;
line-height:1.6;
}
</style>
</head>
<body>
<noscript>
JavaScript seems to be disabled.<br>
This page wont show your subscriptions without it.<br>
You can still open channels directly by clicking their names below.
</noscript>
<div class="layout">
<aside>
<div>
<header>
<h1>my poke</h1>
</header>
<div class="search-bar js-only">
<input id="search" type="text" placeholder="search ur channels">
<button id="clearSearch">clear</button>
</div>
<div class="sort js-only">
<button id="sortAZ" class="active">a → z</button>
<button id="sortZA">z → a</button>
</div>
<%
const subKeys = (userSubs && Object.keys(userSubs)) || [];
const subs = subKeys.map(id => ({
id,
name: userSubs[id]?.channelName || "unknown channel",
avatar: userSubs[id]?.avatar
}));
subs.sort((a,b)=>a.name.localeCompare(b.name,'en',{sensitivity:'base'}));
%>
<div class="channel-list" id="channelList">
<% subs.forEach(c => { %>
<div class="channel" data-id="<%= c.id %>" data-name="<%= c.name.toLowerCase() %>">
<a href="/channel?id=<%= c.id %>" target="_blank" class="channel-name-link" style="display:flex;align-items:center;gap:10px;flex:1;">
<img src="<%= c.avatar %>" alt="">
<div class="channel-name"><%= c.name %></div>
</a>
<a class="pill--unsub" href="/api/remove-channel-sub?ID=<%= encodeURIComponent(userid) %>&channelID=<%= c.id %>" aria-label="Unsubscribe from <%= c.name %>">unsub</a>
</div>
<% }) %>
</div>
</div>
<div class="sidebar-footer">
<span class="uid">logged in as <%= userid %></span>
<a href="/privacy" class="privacy-link">privacy policy</a>
</div>
</aside>
<main class="js-only">
<div class="toolbar">
<span id="channelTitle">nothing selected yet</span>
<a href="/api/get-channel-subs?ID=<%= encodeURIComponent(userid) %>" class="btn">view json</a>
</div>
<div class="viewer" id="viewer">
<div class="placeholder">select a channel on the left to view it</div>
</div>
</main>
</div>
<script type="text/javascript" id="poke-subs" data-name="My Poke Subscriptions JS">
/*
@licstart
Copyright (C) 2024 Poke Project
GPLv3-or-later notice below
@licend
*/
document.addEventListener("DOMContentLoaded",()=>{
const channels=[...document.querySelectorAll(".channel")];
const search=document.getElementById("search");
const clear=document.getElementById("clearSearch");
const sortAZ=document.getElementById("sortAZ");
const sortZA=document.getElementById("sortZA");
const list=document.getElementById("channelList");
const viewer=document.getElementById("viewer");
const title=document.getElementById("channelTitle");
// JS override only if JavaScript is active
document.querySelectorAll(".channel-name-link").forEach(link=>{
link.addEventListener("click",e=>{
e.preventDefault();
const parent=link.closest(".channel");
const id=parent.dataset.id;
const name=parent.querySelector(".channel-name").textContent;
channels.forEach(c=>c.classList.remove("active"));
parent.classList.add("active");
renderIframe(id,name);
});
});
function renderIframe(id,name){
viewer.innerHTML=`
<div class="close-view" title="close">✖</div>
<iframe sandbox="allow-scripts allow-same-origin allow-popups" src="/channel?id=${encodeURIComponent(id)}&embedchannelsubsfeed=true" loading="lazy" allowfullscreen></iframe>`;
title.textContent=name;
const closeBtn=viewer.querySelector(".close-view");
closeBtn.addEventListener("click",()=>{
viewer.innerHTML='<div class="placeholder">select a channel on the left to view it</div>';
channels.forEach(c=>c.classList.remove("active"));
title.textContent="nothing selected yet";
});
}
function filter(){
const term=search.value.toLowerCase();
channels.forEach(c=>{
c.style.display=c.dataset.name.includes(term)?'flex':'none';
});
}
search?.addEventListener("input",filter);
clear?.addEventListener("click",()=>{search.value='';filter();search.focus();});
function sortList(asc){
const items=[...channels];
items.sort((a,b)=>asc?a.dataset.name.localeCompare(b.dataset.name):b.dataset.name.localeCompare(a.dataset.name));
items.forEach(i=>list.appendChild(i));
}
sortAZ?.addEventListener("click",()=>{sortAZ.classList.add("active");sortZA.classList.remove("active");sortList(true);});
sortZA?.addEventListener("click",()=>{sortZA.classList.add("active");sortAZ.classList.remove("active");sortList(false);});
});
</script>
</body>
</html>