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

438 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}
.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;
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;
}
.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{
margin-top:8px;
font-size:12.5px;
color:var(--accent);
text-decoration:none;
align-self:flex-start;
transition:opacity .2s ease;
}
.privacy-link:hover{opacity:0.7}
.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;
cursor:pointer;
}
.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;
}
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 (oh noes!) <br>
This page wont show your subscriptions without it.<br>
Please enable JavaScript or use a browser that supports scripts.
</noscript>
<div class="layout">
<aside>
<header>
<h1>my poke</h1>
<span class="uid">logged in as <%= userid %></span>
<a href="/privacy" class="privacy-link">privacy stuff</a>
</header>
<div class="search-bar">
<input id="search" type="text" placeholder="search ur channels">
<button id="clearSearch">clear</button>
</div>
<div class="sort">
<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() %>">
<img src="<%= c.avatar %>" alt="">
<div class="channel-name"><%= c.name %></div>
</div>
<% }) %>
</div>
</aside>
<main>
<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">u can 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 The following is the entire license notice for the
JavaScript code in this page.
Copyright (C) 2024 Poke Project
The JavaScript code in this page is free software: you can
redistribute it and/or modify it under the terms of the GNU
General Public License (GNU GPL) as published by the Free Software
Foundation, either version 3 of the License, or (at your option)
any later version. The code is distributed WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
As additional permission under GNU GPL version 3 section 7, you
may distribute non-source (e.g., minimized or compacted) forms of
that code without the copy of the GNU GPL normally required by
section 4, provided you include this license notice and a URL
through which recipients can access the Corresponding Source.
@licend The above is the entire license notice
for the JavaScript code in this page.
*/
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");
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">u can select a channel on the left to view it</div>';
channels.forEach(c=>c.classList.remove("active"));
title.textContent="nothing selected yet";
});
}
channels.forEach(ch=>{
ch.addEventListener("click",()=>{
channels.forEach(c=>c.classList.remove("active"));
ch.classList.add("active");
renderIframe(ch.dataset.id,ch.querySelector(".channel-name").textContent);
});
});
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();});
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);
});
function sortList(asc){
const items=[...channels];
items.sort((a,b)=>{
return asc? a.dataset.name.localeCompare(b.dataset.name)
: b.dataset.name.localeCompare(a.dataset.name);
});
items.forEach(i=>list.appendChild(i));
}
});
</script>
</body>
</html>