Pray for me

This commit is contained in:
HighImKevin committed 2026-09-10 07:25:06 -04:00
1 parent bd45be2fd6
commit 1dbb3b8518
98 files changed
+47471 -10558

No files matched your search

+159 -20
View File
@@ -1,6 +1,6 @@
// KefinTweaks Toaster
// Provides toast notification functionality using Jellyfin's existing toast system
// Usage: window.KefinTweaksToaster.toast(message, duration)
// Usage: window.KefinTweaksToaster.toast(message, duration, autoHide, options)
(function() {
'use strict';
@@ -11,6 +11,54 @@
const ERR = (...args) => console.error('[KefinTweaks Toaster]', ...args);
LOG('Initializing');
let stylesInjected = false;
function ensureToastStyles() {
if (stylesInjected) return;
stylesInjected = true;
const style = document.createElement('style');
style.textContent = `
.toast.kefin-toast-rich {
display: flex;
align-items: center;
gap: 0.75em;
max-width: min(28rem, calc(100vw - 2rem));
}
.toast.kefin-toast-rich .kefin-toast-message {
flex: 1 1 auto;
min-width: 0;
line-height: 1.35;
}
.toast.kefin-toast-rich .kefin-toast-link {
color: inherit;
text-decoration: underline;
cursor: pointer;
}
.toast.kefin-toast-rich .kefin-toast-action {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
justify-content: center;
width: 2rem;
height: 2rem;
padding: 0;
margin: 0;
border: none;
border-radius: 50%;
background: transparent;
color: inherit;
cursor: pointer;
}
.toast.kefin-toast-rich .kefin-toast-action:hover {
background: rgba(255, 255, 255, 0.12);
}
.toast.kefin-toast-rich .kefin-toast-action .material-icons {
font-size: 1.25rem;
}
`;
document.head.appendChild(style);
}
/**
* Get the toast container
@@ -54,14 +102,117 @@
// Default to 3 seconds
return 3000;
}
function dismissToastElement(toastElement, container, customContainer) {
if (!toastElement?.parentNode) return;
toastElement.classList.add('toastHide');
setTimeout(() => {
if (toastElement.parentNode) {
toastElement.parentNode.removeChild(toastElement);
}
if (customContainer && container && !container.querySelector('.toast')) {
container.remove();
}
}, 3000);
}
function buildToastContent(toastElement, message, options) {
const link = options?.link;
const action = options?.action;
const rich = !!(link || action);
if (!rich) {
toastElement.textContent = message;
return;
}
ensureToastStyles();
toastElement.classList.add('kefin-toast-rich');
const messageEl = document.createElement('span');
messageEl.className = 'kefin-toast-message';
if (link?.label && typeof message === 'string' && message.includes(link.label)) {
const idx = message.indexOf(link.label);
if (idx > 0) {
messageEl.appendChild(document.createTextNode(message.slice(0, idx)));
}
const anchor = document.createElement('a');
anchor.className = 'kefin-toast-link';
anchor.href = link.href || '#';
anchor.textContent = link.label;
anchor.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
if (typeof link.onClick === 'function') {
link.onClick();
} else if (link.href) {
if (window.Dashboard?.navigate) {
window.Dashboard.navigate(link.href);
} else {
window.location.hash = link.href.startsWith('#') ? link.href : `#/${link.href}`;
}
}
});
messageEl.appendChild(anchor);
const after = message.slice(idx + link.label.length);
if (after) {
messageEl.appendChild(document.createTextNode(after));
}
} else {
messageEl.textContent = message;
if (link?.label) {
messageEl.appendChild(document.createTextNode(' '));
const anchor = document.createElement('a');
anchor.className = 'kefin-toast-link';
anchor.href = link.href || '#';
anchor.textContent = link.label;
anchor.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
if (typeof link.onClick === 'function') link.onClick();
else if (link.href && window.Dashboard?.navigate) {
window.Dashboard.navigate(link.href);
}
});
messageEl.appendChild(anchor);
}
}
toastElement.appendChild(messageEl);
if (action && typeof action.onClick === 'function') {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'kefin-toast-action';
btn.title = action.label || 'Undo';
btn.setAttribute('aria-label', action.label || 'Undo');
const icon = document.createElement('span');
icon.className = 'material-icons';
icon.setAttribute('aria-hidden', 'true');
icon.textContent = action.icon || 'undo';
btn.appendChild(icon);
btn.addEventListener('click', (ev) => {
ev.preventDefault();
ev.stopPropagation();
try {
action.onClick();
} catch (e) {
ERR('Toast action failed:', e);
}
});
toastElement.appendChild(btn);
}
}
/**
* Display a toast message
* @param {string} message - The message to display
* @param {string|number} duration - Duration in seconds
* @param {boolean} autoHide - Whether to automatically hide the toast after duration (default: true)
* @param {{ link?: { label: string, href?: string, onClick?: Function }, action?: { icon?: string, label?: string, onClick: Function } }} [options]
* @returns {{ dismiss: Function, element: HTMLElement }|undefined}
*/
function toast(message, duration = '3', autoHide = true) {
function toast(message, duration = '3', autoHide = true, options = null) {
if (!message) {
WARN('Toast message is required');
return;
@@ -88,7 +239,7 @@
// Create the toast element with just 'toast' class initially
const toastElement = document.createElement('div');
toastElement.className = 'toast';
toastElement.textContent = message;
buildToastContent(toastElement, message, options || {});
// Add to container first
container.appendChild(toastElement);
@@ -100,6 +251,8 @@
// Now add toastVisible class - the browser will see the state change and apply the transition
toastElement.classList.add('toastVisible');
const dismiss = () => dismissToastElement(toastElement, container, customContainer);
// Only set up auto-hide if enabled
if (autoHide) {
@@ -108,25 +261,11 @@
// After the duration, start the fade-out
setTimeout(() => {
if (!toastElement.parentNode) {
return; // Already removed
}
// Add toastHide class to trigger fade-out
toastElement.classList.add('toastHide');
// Wait 1 second for Jellyfin's CSS to handle the fade-out, then remove
setTimeout(() => {
if (toastElement.parentNode) {
toastElement.parentNode.removeChild(toastElement);
LOG(`Removed toast: "${message}"`);
if (customContainer) {
container.remove();
}
}
}, 3000);
dismiss();
}, durationMs);
}
return { dismiss, element: toastElement };
}
// Expose toaster to global scope