js refactoring; stylesheet optimized; starterkit entwicklung

This commit is contained in:
Alexander Müller 2024-12-10 22:53:16 +01:00
parent eda868da25
commit 4affe858bc
9 changed files with 354 additions and 287 deletions

282
code.js
View file

@ -1,282 +0,0 @@
document.addEventListener('DOMContentLoaded', function () {
const container = document.getElementById('starterkit-container');
const shuffleContainers = document.querySelectorAll('.shuffle-container');
const seed = Date.now();
// Lade die Haupt-Konfigurationsdatei
fetch('config.json')
.then(response => response.json())
.then(config => {
const starterkitFiles = config.starterkits;
// Lade alle StarterKit-Dateien
return Promise.all(
starterkitFiles.map(file => fetch(file).then(res => res.json()))
);
})
.then(starterkits => {
// Verarbeite die geladenen StarterKits
starterkits.forEach(kit => {
const kitElement = createStarterKitElement(kit);
container.appendChild(kitElement);
});
// Shuffle-Logik anwenden
shuffleContainers.forEach(container => {
seededShuffleChildren(container, seed);
});
// Zusätzliche Funktionalität für StarterKits hinzufügen
enhanceStarterKits();
})
.catch(error => console.error('Fehler beim Laden der StarterKits:', error));
});
/**
* Erstellt das HTML für ein StarterKit
*/
function createStarterKitElement(kit) {
const kitElement = document.createElement('div');
kitElement.classList.add('starterkit');
const title = document.createElement('h3');
title.textContent = kit.name;
const description = document.createElement('p');
description.textContent = kit.description;
const authorContainer = document.createElement('div');
authorContainer.classList.add('autor');
const authorLink = document.createElement('a');
authorLink.classList.add('account');
authorLink.href = kit.author;
authorLink.target = '_blank';
fetchProfile(kit.author, { updateElement: authorLink });
authorContainer.textContent = 'zusammengestellt von ';
authorContainer.appendChild(authorLink);
kitElement.appendChild(title);
kitElement.appendChild(description);
kitElement.appendChild(authorContainer);
const accountCountElement = document.createElement('div');
accountCountElement.classList.add('account-count');
accountCountElement.textContent = `${kit.accounts.length} Profile`;
kitElement.appendChild(accountCountElement);
// Speichere die Accounts in einem Dataset für späteres Laden
kitElement.dataset.accounts = JSON.stringify(kit.accounts);
return kitElement;
}
/**
* Zusätzliche Funktionen für StarterKits
*/
function enhanceStarterKits() {
const starterkits = document.querySelectorAll('.starterkit');
starterkits.forEach(kit => {
kit.addEventListener('click', function () {
const title = kit.querySelector('h3').textContent;
const accounts = JSON.parse(kit.dataset.accounts || '[]');
const popup = createRecommendationPopup(accounts, title);
document.body.appendChild(popup);
});
});
}
/**
* Erstellt ein Pop-up für die Account-Empfehlungen
*/
function createRecommendationPopup(accounts, title) {
const overlay = document.createElement('div');
overlay.classList.add('recommendation-popup');
const content = document.createElement('div');
content.classList.add('recommendation-popup-content');
const header = document.createElement('div');
header.classList.add('recommendation-popup-header');
const titleElement = document.createElement('h3');
titleElement.textContent = title;
const closeButton = document.createElement('button');
closeButton.classList.add('close-popup');
closeButton.textContent = '×';
closeButton.addEventListener('click', () => overlay.remove());
header.appendChild(titleElement);
header.appendChild(closeButton);
const body = document.createElement('div');
body.classList.add('recommendation-popup-body');
accounts.forEach(accountUrl => {
fetchProfile(accountUrl, { createCard: true })
.then(card => body.appendChild(card))
.catch(error => console.error(`Fehler beim Laden des Accounts: ${accountUrl}`, error));
});
content.appendChild(header);
content.appendChild(body);
overlay.appendChild(content);
overlay.addEventListener('click', function (event) {
if (event.target === overlay) {
overlay.remove();
}
});
return overlay;
}
/**
* Ruft Profilinformationen ab und aktualisiert ein DOM-Element oder erstellt eine Profil-Kachel.
*/
function fetchProfile(profileUrl, options = {}) {
const { updateElement = null, createCard = false } = options;
return new Promise((resolve, reject) => {
try {
const url = new URL(profileUrl);
const pathParts = url.pathname.split('/');
if (pathParts.length > 1 && pathParts[1].startsWith('@')) {
const handle = pathParts[1].substring(1);
const instance = url.hostname;
const lookupUrl = `https://${instance}/api/v1/accounts/lookup?acct=${handle}`;
fetch(lookupUrl)
.then(response => response.json())
.then(data => {
if (updateElement) {
// Aktualisiere das übergebene Element
updateElement.textContent = data.display_name || data.username;
const avatarImage = document.createElement('img');
avatarImage.classList.add('account-avatar');
avatarImage.alt = data.avatar ? `Profilbild von ${data.username}` : 'Profilbild nicht verfügbar';
avatarImage.src = data.avatar || '';
updateElement.prepend(avatarImage);
const serverClass = getServerClass(instance);
if (serverClass) {
updateElement.classList.add(serverClass);
}
resolve(updateElement);
} else if (createCard) {
// Erstelle eine Profil-Kachel
const card = createAccountCard(data, profileUrl, instance);
resolve(card);
}
})
.catch(error => {
console.error(`Fehler beim Abrufen des Profils für ${profileUrl}:`, error);
reject(error);
});
}
} catch (error) {
console.error('Ungültige URL:', profileUrl, error);
reject(error);
}
});
}
/**
* Erstellt eine Account-Kachel
*/
function createAccountCard(data, href, instance) {
const link = document.createElement('a');
link.classList.add('account-card-link');
link.href = href;
link.target = '_blank';
const card = document.createElement('div');
card.classList.add('account-card');
const avatarImage = document.createElement('img');
avatarImage.classList.add('account-avatar');
avatarImage.alt = data.avatar ? `Profilbild von ${data.username}` : 'Profilbild nicht verfügbar';
avatarImage.src = data.avatar || '';
const infoContainer = document.createElement('div');
infoContainer.classList.add('account-info');
const displayNameElement = document.createElement('p');
displayNameElement.classList.add('display-name');
displayNameElement.textContent = data.display_name || data.username;
const handleElement = document.createElement('p');
handleElement.classList.add('handle');
const serverHandleElement = document.createElement('span');
serverHandleElement.classList.add('server-handle');
serverHandleElement.textContent = `@${instance}`;
// Ergänze die Klasse basierend auf der Instanz
const serverClass = getServerClass(instance);
if (serverClass) {
serverHandleElement.classList.add(serverClass);
}
handleElement.textContent = `@${data.username}`;
handleElement.appendChild(serverHandleElement);
infoContainer.appendChild(displayNameElement);
infoContainer.appendChild(handleElement);
card.appendChild(avatarImage);
card.appendChild(infoContainer);
link.appendChild(card);
return link;
}
/**
* Deterministisch Kinder neu anordnen
*/
function seededShuffleChildren(container, seed) {
const children = Array.from(container.children);
const shuffled = seededShuffle(children, seed);
shuffled.forEach(child => container.appendChild(child));
}
function seededShuffle(array, seed) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(seededRandom(seed++) * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function seededRandom(seed) {
const x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
function getServerClass(instance) {
const serverClasses = {
'libori.social': 'liboriSocial',
'reliverse.social': 'reliverseSocial',
'kirche.social': 'kircheSocial',
'katholisch.social': 'katholischSocial'
};
return serverClasses[instance] || null;
}
/**
* Extrahiert den Handle (z. B. @benutzer@instanz) aus einer URL
*/
function extractHandleFromURL(url) {
try {
const parsedUrl = new URL(url);
const pathParts = parsedUrl.pathname.split('/');
return pathParts.length > 1 ? pathParts[1] : url;
} catch (error) {
console.error('Ungültige URL:', url);
return url;
}
}

View file

@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Mastodon Instanzen Übersicht</title>
<link rel="stylesheet" href="./fonts/fonts.css?v=1.0.0">
<link rel="stylesheet" href="style.css?v=1.0.0">
<script src="code.js?v=1.0.3" defer></script>
<link rel="stylesheet" href="style.css?v=1.0.1">
<script type="module" src="src/main.js?v=1.0.0" defer></script>
<script src="utils/kjua-0.10.0.min.js"></script>
<meta property="og:title" content="FediKirche.de">

48
src/main.js Normal file
View file

@ -0,0 +1,48 @@
import { createStarterKitElement, enhanceStarterKits } from './utils/starterkit-utils.js';
import { seededShuffleChildren } from './utils/shuffle-utils.js';
async function loadTemplates() {
const response = await fetch('src/template/template.html');
const templateHTML = await response.text();
// Füge die Templates in das DOM ein
const templateContainer = document.createElement('div');
templateContainer.innerHTML = templateHTML;
document.body.appendChild(templateContainer);
}
document.addEventListener('DOMContentLoaded', async function () {
await loadTemplates(); // Templates laden
const container = document.getElementById('starterkit-container');
const shuffleContainers = document.querySelectorAll('.shuffle-container');
const seed = Date.now();
// Lade die Haupt-Konfigurationsdatei
fetch('config.json')
.then(response => response.json())
.then(config => {
const starterkitFiles = config.starterkits;
// Lade alle StarterKit-Dateien
return Promise.all(
starterkitFiles.map(file => fetch(file).then(res => res.json()))
);
})
.then(starterkits => {
// Verarbeite die geladenen StarterKits
starterkits.forEach(kit => {
const kitElement = createStarterKitElement(kit);
container.appendChild(kitElement);
});
// Shuffle-Logik anwenden
shuffleContainers.forEach(container => {
seededShuffleChildren(container, seed);
});
// Zusätzliche Funktionalität für StarterKits hinzufügen
enhanceStarterKits();
})
.catch(error => console.error('Fehler beim Laden der StarterKits:', error));
});

View file

@ -0,0 +1,29 @@
<template id="starterkit-template">
<div class="starterkit">
<h3></h3>
<p></p>
<div class="starterkit-footer">
<div class="profile-count">
<span class="profile-line1"></span><br>
<span class="profile-line2"></span>
</div>
<div class="autor">
<span class="created-by">created by</span>
<a class="account" target="_blank"></a>
</div>
</div>
</div>
</template>
<template id="popup-template">
<div class="recommendation-popup">
<div class="recommendation-popup-content">
<div class="recommendation-popup-header">
<h3></h3>
<button class="close-popup">×</button>
</div>
<div id="account-list" class="recommendation-popup-body"></div>
</div>
</div>
</template>

27
src/utils/popup-utils.js Normal file
View file

@ -0,0 +1,27 @@
import { fetchProfile } from './profile-utils.js';
export function createRecommendationPopup(accounts, title) {
const template = document.getElementById('popup-template').content.cloneNode(true);
const popup = template.querySelector('.recommendation-popup');
popup.querySelector('h3').textContent = title;
const closeButton = popup.querySelector('.close-popup');
closeButton.addEventListener('click', () => popup.remove());
const body = popup.querySelector('.recommendation-popup-body');
accounts.forEach(accountUrl => {
fetchProfile(accountUrl, { createCard: true })
.then(card => body.appendChild(card))
.catch(error => console.error(`Fehler beim Laden des Accounts: ${accountUrl}`, error));
});
popup.addEventListener('click', function (event) {
if (event.target === popup) {
popup.remove();
}
});
return popup;
}

102
src/utils/profile-utils.js Normal file
View file

@ -0,0 +1,102 @@
export async function fetchProfile(profileUrl, options = {}) {
const { updateElement = null, createCard = false } = options;
return new Promise((resolve, reject) => {
try {
const url = new URL(profileUrl);
const pathParts = url.pathname.split('/');
if (pathParts.length > 1 && pathParts[1].startsWith('@')) {
const handle = pathParts[1].substring(1);
const instance = url.hostname;
const lookupUrl = `https://${instance}/api/v1/accounts/lookup?acct=${handle}`;
fetch(lookupUrl)
.then(response => response.json())
.then(data => {
if (updateElement) {
updateElement.textContent = data.display_name || data.username;
const avatarImage = document.createElement('img');
avatarImage.classList.add('account-avatar');
avatarImage.alt = data.avatar ? `Profilbild von ${data.username}` : 'Profilbild nicht verfügbar';
avatarImage.src = data.avatar || '';
updateElement.prepend(avatarImage);
const serverClass = getServerClass(instance);
if (serverClass) {
updateElement.classList.add(serverClass);
}
resolve(updateElement);
} else if (createCard) {
const card = createAccountCard(data, profileUrl, instance);
resolve(card);
}
})
.catch(error => {
console.error(`Fehler beim Abrufen des Profils für ${profileUrl}:`, error);
reject(error);
});
}
} catch (error) {
console.error('Ungültige URL:', profileUrl, error);
reject(error);
}
});
}
export function createAccountCard(data, href, instance) {
const link = document.createElement('a');
link.classList.add('account-card-link');
link.href = href;
link.target = '_blank';
const card = document.createElement('div');
card.classList.add('account-card');
const avatarImage = document.createElement('img');
avatarImage.classList.add('account-avatar');
avatarImage.alt = data.avatar ? `Profilbild von ${data.username}` : 'Profilbild nicht verfügbar';
avatarImage.src = data.avatar || '';
const infoContainer = document.createElement('div');
infoContainer.classList.add('account-info');
const displayNameElement = document.createElement('p');
displayNameElement.classList.add('display-name');
displayNameElement.textContent = data.display_name || data.username;
const handleElement = document.createElement('p');
handleElement.classList.add('handle');
const serverHandleElement = document.createElement('span');
serverHandleElement.classList.add('server-handle');
serverHandleElement.textContent = `@${instance}`;
const serverClass = getServerClass(instance);
if (serverClass) {
serverHandleElement.classList.add(serverClass);
}
handleElement.textContent = `@${data.username}`;
handleElement.appendChild(serverHandleElement);
infoContainer.appendChild(displayNameElement);
infoContainer.appendChild(handleElement);
card.appendChild(avatarImage);
card.appendChild(infoContainer);
link.appendChild(card);
return link;
}
function getServerClass(instance) {
const serverClasses = {
'libori.social': 'liboriSocial',
'reliverse.social': 'reliverseSocial',
'kirche.social': 'kircheSocial',
'katholisch.social': 'katholischSocial'
};
return serverClasses[instance] || null;
}

View file

@ -0,0 +1,18 @@
export function seededShuffleChildren(container, seed) {
const children = Array.from(container.children);
const shuffled = seededShuffle(children, seed);
shuffled.forEach(child => container.appendChild(child));
}
export function seededShuffle(array, seed) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(seededRandom(seed++) * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
export function seededRandom(seed) {
const x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}

View file

@ -0,0 +1,43 @@
import { fetchProfile } from './profile-utils.js';
import { createRecommendationPopup } from './popup-utils.js';
export function createStarterKitElement(kit) {
const template = document.getElementById('starterkit-template').content.cloneNode(true);
const kitElement = template.querySelector('.starterkit');
kitElement.querySelector('h3').textContent = kit.name;
kitElement.querySelector('p').textContent = kit.description;
// Aufteilen der Profilanzahl in zwei Zeilen
const profileCount = kitElement.querySelector('.profile-count');
profileCount.querySelector('.profile-line1').textContent = `Accounts`;
profileCount.querySelector('.profile-line2').textContent = `${kit.accounts.length}`;
// Hinzufügen des Autors mit 'created by'
const authorLink = kitElement.querySelector('.account');
authorLink.href = kit.author;
fetchProfile(kit.author, { updateElement: authorLink }).then(() => {
const avatarImage = authorLink.querySelector('.account-avatar');
if (avatarImage) {
authorLink.appendChild(avatarImage);
}
});
kitElement.dataset.accounts = JSON.stringify(kit.accounts);
return kitElement;
}
export function enhanceStarterKits() {
const starterkits = document.querySelectorAll('.starterkit');
starterkits.forEach(kit => {
kit.addEventListener('click', function () {
const title = kit.querySelector('h3').textContent;
const accounts = JSON.parse(kit.dataset.accounts || '[]');
const popup = createRecommendationPopup(accounts, title);
document.body.appendChild(popup);
});
});
}

View file

@ -175,6 +175,8 @@ section {
transition: transform 0.3s ease, box-shadow 0.3s ease;
cursor: pointer;
position: relative;
min-height: 200px;
padding-bottom: 50px;
}
.starterkit:hover {
@ -182,6 +184,86 @@ section {
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.4);
}
/* StarterKit Footer */
.starterkit-footer {
position: absolute;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
margin: 0 15px;
font-size: 0.85rem;
color: #aaa;
background-color: var(--secondary-background-color);
border-top: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 0 0 15px 15px; /* Runde Ecken unten */
}
.profile-count {
font-size: 0.9rem;
color: #ccc;
text-align: center;
}
.autor {
display: flex;
align-items: center;
gap: 10px;
text-align: left;
margin-top: 10px;
}
.autor-text {
display: flex;
flex-direction: column;
justify-content: center;
}
.created-by {
font-size: 0.75rem;
color: #aaa;
}
.author-name {
font-size: 0.9rem;
color: #fff;
font-weight: bold;
}
.account-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
.account {
font-size: 0.9rem;
color: #ccc;
text-decoration: none;
}
.autor .account-avatar {
width: 30px;
height: 30px;
border-radius: 50%;
margin: 0;
}
.autor a {
text-decoration: none;
color: #ccc;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 5px;
}
/* Pop-up Styling */
.recommendation-popup {
position: fixed;
@ -189,7 +271,7 @@ section {
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.8);
background: rgba(53, 53, 53, 0.8);
display: flex;
justify-content: center;
align-items: center;
@ -197,7 +279,7 @@ section {
}
.recommendation-popup-content {
background-color: var(--secondary-background-color);
background-color: var(--background-color);
border-radius: 15px;
padding: 20px;
width: 90%;
@ -229,7 +311,6 @@ section {
}
.recommendation-popup-body {
flex: 1;
overflow-y: auto;
padding: 10px 10px 0 0;
display: grid;
@ -239,6 +320,7 @@ section {
margin-top: 20px;
list-style-type: none;
}
.account-card-link {
text-decoration: none;
color: inherit;