1225 lines
49 KiB
HTML
1225 lines
49 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Favoriten-Viewer | PonyWave Tools</title>
|
|
|
|
<!-- Open Graph Tags -->
|
|
<meta property="og:title" content="Favoriten-Viewer | PonyWave Tools">
|
|
<meta property="og:description" content="Importiere und visualisiere deine Browser-Lesezeichen im HTML- oder JSON-Format">
|
|
<meta property="og:url" content="https://tools.ponywave.de/favorites_viewer">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:image" content="https://tools.ponywave.de/favicon.png">
|
|
|
|
<link rel="icon" href="https://tools.ponywave.de/favicon.png" type="image/png">
|
|
|
|
<!-- Analytics -->
|
|
<script defer src="https://stats.ponywave.de/script" data-website-id="9ef713d2-adb9-4906-9df5-708d8a8b9131" data-tag="favorites_viewer"></script>
|
|
|
|
<style>
|
|
/* System-Schriften statt Google Fonts */
|
|
@font-face {
|
|
font-family: 'LocalIcons';
|
|
src: url('data:application/font-woff;charset=utf-8;base64,d09GRgABAAAAAAw0AA0AAAAAEWQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABGRlRNAAAMGAAAABoAAAAciAjOh0dERUYAAAv4AAAAHAAAAB4AJwAbT1MvMgAAAZwAAABKAAAAYDgjTEljbWFwAAACNAAAAFYAAAFqzGUt1mdhc3AAAAvwAAAACAAAAAgAAAAQZ2x5ZgAAApgAAAWwAAAH5IwCyDtoZWFkAAABMAAAAC8AAAA2Fkka0WhoZWEAAAFgAAAAHQAAACQHgwOGaG10eAAAAeAAAAA2AAAAQjI4AcRsb2NhAAACegAAABwAAAAcDRYOym1heHAAAAGAAAAAGAAAACAAXgE5bmFtZQAACUgAAADaAAABr62PurJwb3N0AAAKJAAAAIgAAADE4FdUXXjaY2BkYGAA4sV7ZfLj+W2+MnCzMIDADWEzQ+C0VRgY/v/jP8I8mJkHSHJwMDGBRAHC3Q0peNpjYGRgYG7438CQwPqP4R/DPJgBKIICmACZhQVtAAB42mNgZGBgsGVIZ2BmAAEmMI8LSP6HMwAAF74BagAAeNpjYGH+xzyHgZWBgamLaQ8DA0MPhGZ8wGDIyAQUZWBlZoABTGwM7AwqMMXgAGF+SVFhZgYF0GAmMAUAFwkKxgB42mNhgADGUAjNDMxsDP4QmvEfwwE4tgELwwQQLBYGBphYIgCWtAYlAHjaY2BgYGaAYBkGRgYQcAHyGMF8FgYNIM0GpBkZmBj+Mf3X+v+f4QBTGlOueC57LmsuO5S/AKiSEajICIY5HMgywADrUAZGJmxJZiZmFjTN/5gwNCLEYQpQowFAAG8VFHjaXVG7TltBEN3ndnYsuLz12nZm5i9dNeeePNu8hWe7kYvBZ1PeeBhW3lqs2bHpe9sss9nrr3wf3HqaTygpw3JKHH8S29VwKz6w0drjX+sGa7XRyqYzaNgPqZYKU6CysTi8eptJdY3JbbB4wqMEAaXA5A1CqcnykOep6QcRGnUrslpUnBSVN5sr6EJcWBWbEMpqZr6c5kyT+/MK8Yp17xB9pHcuVfAbuVTEeiegx6zYYWjKE1OI1tglz2E0gspHNNKo0zSXJ6H2IgHGLXOIUGLxdZr7SkLY/5UE0RZf6gQ0rLmgPaUxZgDe0tGcxdxB9H/a+NsdcDh9hP66+XmBGcZ5/zMCzEJvr4Nh1f+6gjUNmHe8wZMpjUJBwclAOJVNdaHEATG9AZyzFJolmWTKgTF1ZK6/Kx/Q5Y75Y4KzAv6ZJaVa+LiZ5VzLPOGLBDXf8FaLz8O0QWzmbncWxsY4qUKL/1u9zQ4FLfQjDCx+49p8waVIcbJ1iCLwI4G6CTLUWnac5YGbQJyDXQzwQkpcUSBKZjOFBYoZIPhzJHCJmxlZ4hYXKTAZMopEcVxXUhIKmhzQ1CHDBAdwmLGBjD0PScQMYJsCFPIYYAADZQpEcVzXUhIMGGbQ39AxCJSYwV6CReNzGkMcAuAinEbYoRiCBp4bYdM4LgzQYNq8WColI4VqWiVV6dCVFpGW5dXVUr0kjQ8pUWSkkqVRbZXSFYpTarXeXCSVlJ6TsqVMxUrFSpaTJqqSilUvlLITrCmtPCE5k5UV85WhJVXrEsUZlsClKJC5UjVa7xIql2uFMrJ5UtpCtKq0VCFYo7GsULRSPFolRVAYz0vFKlYoUl5W1AvVq3RJnZrGCkkrKlZJ2VIulYtFUnlGk0rVrJDU8NKEpOZlheKMlhOSFWdYTpgRKU5nBUW6ZOUKlSs0L18pnFGkUl5SypfL1Ur57ALDF6p5QVErxZJCsZzWFHNUXLt2LaJiJ96W+J0IdoIdYTfYE/aFqiAIIkFPEA8SgbOa9Kz7rP9s4GzwbOhs+Gzk7PnZ6Nlfzv559q+z/5wdn/1rtWu1WR1W2iptb9p/2X/bI/Yn9qf2Z/bn9hf2l/Y39rf2d/b39g/2j/aP9u/2n/Zf9t/2P/aIAxa4h49PQIjgxJvHLuATEMMnxCckgk9ERIRPTISInBgJEZEQkRIRMzFiJJEQpBzfIhHLRCQXkVokJSclHYjJLpKRk5ORq06dXEROQU5OQXERJSUluZpMzUyNnEJNpiJXsVAwqyks1FQUlBQW6gpLSgprNaWK0kJWUlMtoOB5s6+6uP0Pz/PteXwHRo+PRw+eiUePHlAeBqaPaSIPDXkQhpQ8DBk9ZM3p4eEBpddHr1+/Gv8PECEZkQAAAQAB//8AD3jatVVNUxtJEM17ezSjkYSkQcIPjNAgJKEPg4UsMBhjwIDw8VGyRzZ2HML6Qv6H+yOc/A95g1PwA3ZveUKCXLPJUZfdIo5VW4WKcvTu3q6TXn9vDu+d01Wqrq6enp55PfWN/e93Nd8MgiCAKVRQUEEUFRMqKIuqInlU9xIQzVeHVNSTvqQmiqbqqZvpQXlk5M+yqiQGVa+WLJtK0rC75F7Zq5SDQWSGsNTgRLXTyFXn1aA6r2UWYu9S735KuV9MvkudYSI1O/vGjUhX1FGWj8OyiQyqKElDUL5g1Eu9+T2+lv7NsZ9Sj8svQyNLVnPGsF20nCKN0PV8nW5WDCdiOVvLnrx4+fVaZjvnOOtbr9+82ZzOFN/ebu8VLg45XRdLRX57qKa8LVaLM273e1kpXoRfJHJJ/BbJJb9Vhj+M7+pVQrwS5EsJ98SLnLtJf+QTIT/Fo5vZH8Xqdu5m56a9n5suvjnbvtraqtPtXNNb06HL8UDfZOUfYw7Mk6YiMqqqfv8xg5qqhCLiTF2sSF91cZkPJVw9QDRR98qoJuqeR3VPDJlDN2qTZUtJq4KKkDT7oQiU5fkn2Wg8O73wIJJCBHH29IKqpjxM5jARyUMmPsAMVEFyoHJVxZJF2IIhS/ql4Ic57NRq6I8LyYXY5WLl7eXCW8qSZnK5jX1fgfz6x9UD0dIWKptvd7e8rXR1bq5KP+eKHM9zpY3X02KVrx+fjTcCNYJYESJYDkWQ4dBFnEHDYI+BNzI0XEaclgUhE3N4ywP8AZNZOZPEMrM805gmJcYOGTvfRf+FfowkjBwnYU5JIrPxQI4iqEGYxkAGAyHsJyRpZMBnBCM5UKSeG5YlxUWcZQC5FPrTZnxcqyUfzdLf2MaHaTbx2kCWHABZ/MWHMwF1G+jbQPMxWwsR2uQ1T2M8y8VzeBNfP+L/MTrJIpAWl+DxxLPfLNxiMt89y7a4uc3OPzSNQNsYYeN/OUfbvsQcHvMceswNSV7k9YcG9JN0IZWDqaVsLgb53AKOzEI4crvMBT2EyQTFHw95eFXGG+MN33M1dINngXP9Hgn4oMDJhV4KebEeCSKzhMgY4iyBPgdcKwcj78C49JF0y+M3/ZF3YjqeqLiPf74JC9Ci+N0bI3M7OXnnMdgQdLM6vZOszDxuNCrl5X/Wpms7e2Wv1j4+JN+sjR/V08+XVtaPD7ceOC5zrKqbW9vHL9ZO4YNXbR1Mzs9Nz0+Ek+1WY3oiHB/gKcuHI/fv3kueq4vL+eSFWo1a7cb+zknL67Tb+21vfWO3Y7vtp8vJxGLiSHRuT/5cQnvx8HwouRh97uK502FPG1Nn4ZnliYeWnF5eGXR2dxsrUw/G5fqKH/0y/Phpj/y12/zzVvtfdjGBZHjaY2BkYGAA4qXmQQXx/DZfGbiZGEDghrCZImTazP/PmAebu4Akhwg3EwOIBgBDsQnmeNpjYGRgYG7438AQw/qP4R/DPJgBKIICmACZhQVtAAB42mNgZGBgYPjHwMrAAgACTEDMyAAScwDzGAAFeABrAAB42m2OMQ7CQAxFfwIhQQEUdEhUF4RQpFj1Cg3JMaLnGJyAE3CFIdnZYSh2R3m2//znTwwAS5zIsT2Grdbu8xgd9+ynV+88ibbxyVQ+W3V+I1J/nfBGYsqeZZ+XT177nrTcQ6EZcTShHfRvxH9yZyaxpcwmYY03dH4dQx/UyNbY4YAaqZP6gG7/R7VZzz9XSy8yDIBa1MWXOS7MoVTX5m4rjVYqQwUJH8AFD00AAAB42mNgYgCD/8QMaQzYAD8jAzMDMwNTCWMZYxdjH+MI4xTjIo4CRnYGdsZaxoUMQgwKDLYMTgzuDFEM6QzLGeUZNRjNGO0ZdRgNGXXE2AEAPAUUEAB42mNgZGBg4GIQYzBjYHJx8wlh4MtJLMljkGJgAYoz/P/PwASkOBifM0gobGHgB8piYoQAACRwCaUAAAFTTQSdAAA=') format('woff');
|
|
font-weight: normal;
|
|
font-style: normal;
|
|
}
|
|
|
|
:root {
|
|
--primary-color: #3f51b5;
|
|
--primary-light: #757de8;
|
|
--primary-dark: #002984;
|
|
--secondary-color: #ff4081;
|
|
--secondary-light: #ff79b0;
|
|
--secondary-dark: #c60055;
|
|
--text-on-primary: #ffffff;
|
|
--text-on-secondary: #000000;
|
|
--background-color: #f5f5f5;
|
|
--surface-color: #ffffff;
|
|
--border-radius: 8px;
|
|
--box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
--transition-speed: 0.3s;
|
|
}
|
|
|
|
* {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
line-height: 1.6;
|
|
background-color: var(--background-color);
|
|
color: #333;
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
header {
|
|
background-color: var(--primary-color);
|
|
color: var(--text-on-primary);
|
|
padding: 20px;
|
|
border-radius: var(--border-radius);
|
|
margin-bottom: 20px;
|
|
box-shadow: var(--box-shadow);
|
|
text-align: center;
|
|
}
|
|
|
|
h1 {
|
|
font-weight: 500;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.app-description {
|
|
font-weight: 400;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
margin-top: 10px;
|
|
background-color: rgba(0, 0, 0, 0.1);
|
|
padding: 8px 15px;
|
|
border-radius: 20px;
|
|
display: inline-block;
|
|
}
|
|
|
|
.local-data-box {
|
|
margin-top: 15px;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.local-data-notice {
|
|
font-weight: 400;
|
|
font-size: 0.9em;
|
|
background-color: rgba(0, 0, 0, 0.1);
|
|
padding: 8px 15px;
|
|
border-radius: 20px;
|
|
display: inline-block;
|
|
color: var(--text-on-primary);
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.card {
|
|
background-color: var(--surface-color);
|
|
padding: 25px;
|
|
border-radius: var(--border-radius);
|
|
box-shadow: var(--box-shadow);
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.upload-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
|
|
.file-upload {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
width: 100%;
|
|
max-width: 500px;
|
|
margin: 20px 0;
|
|
}
|
|
|
|
.file-upload-label {
|
|
width: 100%;
|
|
text-align: center;
|
|
margin-bottom: 15px;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.file-format-select {
|
|
margin: 15px 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
width: 100%;
|
|
}
|
|
|
|
.format-option {
|
|
padding: 10px 15px;
|
|
border: 2px solid #ddd;
|
|
border-radius: var(--border-radius);
|
|
cursor: pointer;
|
|
transition: all var(--transition-speed);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.format-option.active {
|
|
border-color: var(--primary-color);
|
|
background-color: var(--primary-light);
|
|
color: var(--text-on-primary);
|
|
}
|
|
|
|
.custom-file-input {
|
|
display: none;
|
|
}
|
|
|
|
.file-upload-btn {
|
|
display: inline-block;
|
|
padding: 12px 24px;
|
|
background-color: var(--primary-color);
|
|
color: var(--text-on-primary);
|
|
border-radius: var(--border-radius);
|
|
cursor: pointer;
|
|
transition: background-color var(--transition-speed);
|
|
text-align: center;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.file-upload-btn::before {
|
|
content: "📂";
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.file-upload-btn:hover {
|
|
background-color: var(--primary-dark);
|
|
}
|
|
|
|
.file-name {
|
|
margin-top: 10px;
|
|
font-style: italic;
|
|
color: #666;
|
|
}
|
|
|
|
.favorites-card {
|
|
position: relative;
|
|
min-height: 100px;
|
|
}
|
|
|
|
#favoritesOutput {
|
|
padding: 10px;
|
|
}
|
|
|
|
#favoritesOutput details {
|
|
margin: 5px 0;
|
|
border-radius: var(--border-radius);
|
|
overflow: hidden;
|
|
}
|
|
|
|
#favoritesOutput details + details,
|
|
#favoritesOutput a + details,
|
|
#favoritesOutput details + a,
|
|
#favoritesOutput a + a {
|
|
margin-top: 8px;
|
|
}
|
|
|
|
#favoritesOutput summary {
|
|
font-weight: 500;
|
|
cursor: pointer;
|
|
padding: 8px 12px;
|
|
border-radius: var(--border-radius);
|
|
background-color: #eaeaea;
|
|
display: flex;
|
|
align-items: center;
|
|
transition: background-color var(--transition-speed);
|
|
}
|
|
|
|
#favoritesOutput summary:hover {
|
|
background-color: #d5d5d5;
|
|
}
|
|
|
|
#favoritesOutput summary::before {
|
|
content: "📁";
|
|
margin-right: 10px;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
#favoritesOutput details[open] > summary::before {
|
|
content: "📂";
|
|
}
|
|
|
|
#favoritesOutput > details {
|
|
margin-left: 0;
|
|
}
|
|
|
|
#favoritesOutput details details {
|
|
margin-left: 20px;
|
|
}
|
|
|
|
#favoritesOutput a {
|
|
display: flex;
|
|
align-items: center;
|
|
color: #333;
|
|
text-decoration: none;
|
|
padding: 6px 12px;
|
|
margin: 4px 0;
|
|
margin-left: 20px;
|
|
border-radius: var(--border-radius);
|
|
transition: background-color var(--transition-speed);
|
|
}
|
|
|
|
#favoritesOutput > a {
|
|
margin-left: 0;
|
|
}
|
|
|
|
#favoritesOutput a:hover {
|
|
background-color: #eaeaea;
|
|
text-decoration: none;
|
|
}
|
|
|
|
#favoritesOutput details a {
|
|
margin-left: 20px;
|
|
}
|
|
|
|
#favoritesOutput a::before {
|
|
content: "🔗";
|
|
margin-right: 10px;
|
|
color: var(--secondary-color);
|
|
}
|
|
|
|
.bookmark-info {
|
|
margin-left: 10px;
|
|
font-size: 0.8em;
|
|
color: #888;
|
|
}
|
|
|
|
.bookmark-tags {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 5px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.bookmark-tag {
|
|
background-color: #eaeaea;
|
|
padding: 2px 6px;
|
|
border-radius: 10px;
|
|
font-size: 0.7em;
|
|
color: #666;
|
|
}
|
|
|
|
.bookmark-icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
margin-right: 10px;
|
|
object-fit: contain;
|
|
}
|
|
|
|
.status-indicators {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-top: 15px;
|
|
}
|
|
|
|
#loadingIndicator, #errorIndicator {
|
|
padding: 10px 20px;
|
|
font-weight: 500;
|
|
border-radius: var(--border-radius);
|
|
display: none;
|
|
}
|
|
|
|
#loadingIndicator {
|
|
background-color: #e3f2fd;
|
|
color: #1976d2;
|
|
}
|
|
|
|
/* Icon für Loading Indicator */
|
|
#loadingIndicator::before {
|
|
content: "\e804";
|
|
font-family: 'LocalIcons';
|
|
margin-right: 8px;
|
|
}
|
|
|
|
#errorIndicator {
|
|
background-color: #ffebee;
|
|
color: #d32f2f;
|
|
}
|
|
|
|
#successIndicator {
|
|
padding: 10px 20px;
|
|
font-weight: 500;
|
|
border-radius: var(--border-radius);
|
|
display: none;
|
|
background-color: #e8f5e9;
|
|
color: #2e7d32;
|
|
}
|
|
|
|
/* Icon für Success Indicator */
|
|
#successIndicator::before {
|
|
content: "✅";
|
|
margin-right: 8px;
|
|
}
|
|
|
|
.stats-container {
|
|
margin-top: 20px;
|
|
padding: 15px;
|
|
background-color: #f0f0f0;
|
|
border-radius: var(--border-radius);
|
|
display: none;
|
|
}
|
|
|
|
.stats-title {
|
|
font-weight: 500;
|
|
margin-bottom: 10px;
|
|
color: var(--primary-color);
|
|
}
|
|
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
}
|
|
|
|
.stat-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 15px;
|
|
background-color: white;
|
|
border-radius: var(--border-radius);
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
overflow: hidden;
|
|
width: 100%;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
color: var(--primary-color);
|
|
word-break: break-all;
|
|
overflow-wrap: break-word;
|
|
max-width: 100%;
|
|
text-align: center;
|
|
white-space: normal;
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: 14px;
|
|
color: #666;
|
|
margin-top: 8px;
|
|
}
|
|
|
|
.domain-text {
|
|
width: 100%;
|
|
display: block;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.domain-count {
|
|
font-size: 14px;
|
|
display: block;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.search-container {
|
|
margin: 10px 0;
|
|
width: 100%;
|
|
}
|
|
|
|
.search-input {
|
|
width: 100%;
|
|
padding: 10px 15px;
|
|
border: 1px solid #ddd;
|
|
border-radius: var(--border-radius);
|
|
font-size: 16px;
|
|
transition: border-color var(--transition-speed);
|
|
}
|
|
|
|
.search-input:focus {
|
|
border-color: var(--primary-color);
|
|
outline: none;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.file-format-select {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.format-option {
|
|
width: 100%;
|
|
text-align: center;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
footer {
|
|
margin-top: 40px;
|
|
background-color: #f5f5f5;
|
|
padding: 15px;
|
|
text-align: center;
|
|
border-top: 1px solid #ddd;
|
|
color: #666;
|
|
}
|
|
|
|
.dark-mode footer {
|
|
background-color: #333;
|
|
color: #ccc;
|
|
border-top: 1px solid #444;
|
|
}
|
|
|
|
footer a {
|
|
color: var(--primary-color);
|
|
text-decoration: none;
|
|
transition: opacity 0.3s;
|
|
}
|
|
|
|
footer a:hover {
|
|
opacity: 0.8;
|
|
text-decoration: underline;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<h1>Favoriten-Viewer</h1>
|
|
<p class="app-description">Importiere und visualisiere deine Browser-Lesezeichen im HTML- oder JSON-Format</p>
|
|
<div class="local-data-box">
|
|
<span class="local-data-notice">🔒 Alle Dateien werden lokal in deinem Browser verarbeitet. Es werden keine Daten an Server gesendet.</span>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="container">
|
|
<div class="card upload-card">
|
|
<div class="file-upload">
|
|
<div class="file-format-select">
|
|
<div class="format-option active" data-format="html">Chrome (HTML)</div>
|
|
<div class="format-option" data-format="json">Firefox (JSON/HTML)</div>
|
|
</div>
|
|
|
|
<label for="fileInput" class="file-upload-btn">
|
|
Lesezeichen-Datei auswählen
|
|
</label>
|
|
<input type="file" id="fileInput" class="custom-file-input" accept=".html,.json">
|
|
<div class="file-name" id="fileName">Keine Datei ausgewählt</div>
|
|
</div>
|
|
|
|
<div class="status-indicators">
|
|
<div id="loadingIndicator">
|
|
⏳ Verarbeite Datei...
|
|
</div>
|
|
<div id="errorIndicator"></div>
|
|
<div id="successIndicator"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="search-container">
|
|
<input type="text" class="search-input" id="searchInput" placeholder="Lesezeichen durchsuchen...">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card favorites-card">
|
|
<div id="favoritesOutput">
|
|
<!-- Hier werden die Favoriten angezeigt -->
|
|
<div style="text-align: center; padding: 20px; color: #666;">
|
|
<div style="font-size: 48px; color: #ccc; margin-bottom: 10px;">📚</div>
|
|
<p>Wähle eine Datei aus, um deine Lesezeichen anzuzeigen.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card stats-container" id="statsContainer">
|
|
<h3 class="stats-title">Lesezeichen-Statistiken</h3>
|
|
<div class="stats-grid" id="statsGrid">
|
|
<!-- Hier werden die Statistiken angezeigt -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<footer>
|
|
<p><a href="https://tools.ponywave.de/">Zurück zur Startseite</a> | © <span id="current-year">2023</span> Akamaru | Made with ❤️ by Claude</p>
|
|
</footer>
|
|
|
|
<script>
|
|
const fileInput = document.getElementById('fileInput');
|
|
const fileName = document.getElementById('fileName');
|
|
const outputDiv = document.getElementById('favoritesOutput');
|
|
const loadingIndicator = document.getElementById('loadingIndicator');
|
|
const errorIndicator = document.getElementById('errorIndicator');
|
|
const successIndicator = document.getElementById('successIndicator');
|
|
const searchInput = document.getElementById('searchInput');
|
|
const statsContainer = document.getElementById('statsContainer');
|
|
const statsGrid = document.getElementById('statsGrid');
|
|
const formatOptions = document.querySelectorAll('.format-option');
|
|
|
|
let selectedFormat = 'html';
|
|
let allBookmarks = [];
|
|
let statistics = {
|
|
totalBookmarks: 0,
|
|
totalFolders: 0,
|
|
domains: {},
|
|
oldestBookmark: null,
|
|
newestBookmark: null,
|
|
tagsCount: {}
|
|
};
|
|
|
|
// Format-Auswahl
|
|
formatOptions.forEach(option => {
|
|
option.addEventListener('click', () => {
|
|
formatOptions.forEach(opt => opt.classList.remove('active'));
|
|
option.classList.add('active');
|
|
selectedFormat = option.dataset.format;
|
|
|
|
// Update file input accept attribute
|
|
if (selectedFormat === 'html') {
|
|
fileInput.setAttribute('accept', '.html');
|
|
} else {
|
|
fileInput.setAttribute('accept', '.json,.html');
|
|
}
|
|
});
|
|
});
|
|
|
|
// Datei-Auswahl
|
|
fileInput.addEventListener('change', (event) => {
|
|
const file = event.target.files[0];
|
|
if (!file) {
|
|
outputDiv.innerHTML = '<div style="text-align: center; padding: 20px; color: #666;"><div style="font-size: 48px; color: #ccc; margin-bottom: 10px;">📚</div><p>Keine Datei ausgewählt.</p></div>';
|
|
fileName.textContent = 'Keine Datei ausgewählt';
|
|
errorIndicator.style.display = 'none';
|
|
loadingIndicator.style.display = 'none';
|
|
successIndicator.style.display = 'none';
|
|
statsContainer.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
fileName.textContent = file.name;
|
|
|
|
// Überprüfen, ob die Datei dem ausgewählten Format entspricht
|
|
const isHtml = file.type === 'text/html' || file.name.endsWith('.html');
|
|
const isJson = file.type === 'application/json' || file.name.endsWith('.json');
|
|
|
|
if (selectedFormat === 'html' && !isHtml) {
|
|
outputDiv.innerHTML = '';
|
|
errorIndicator.textContent = `Fehler: Bitte lade eine HTML-Datei hoch.`;
|
|
errorIndicator.style.display = 'block';
|
|
loadingIndicator.style.display = 'none';
|
|
successIndicator.style.display = 'none';
|
|
statsContainer.style.display = 'none';
|
|
return;
|
|
} else if (selectedFormat === 'json' && !isJson && !isHtml) {
|
|
outputDiv.innerHTML = '';
|
|
errorIndicator.textContent = `Fehler: Bitte lade eine JSON- oder HTML-Datei hoch.`;
|
|
errorIndicator.style.display = 'block';
|
|
loadingIndicator.style.display = 'none';
|
|
successIndicator.style.display = 'none';
|
|
statsContainer.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
const reader = new FileReader();
|
|
|
|
reader.onloadstart = () => {
|
|
outputDiv.innerHTML = ''; // Vorherige Ausgabe löschen
|
|
errorIndicator.style.display = 'none';
|
|
successIndicator.style.display = 'none';
|
|
loadingIndicator.style.display = 'block';
|
|
statsContainer.style.display = 'none';
|
|
|
|
// Statistik zurücksetzen
|
|
statistics = {
|
|
totalBookmarks: 0,
|
|
totalFolders: 0,
|
|
domains: {},
|
|
oldestBookmark: null,
|
|
newestBookmark: null,
|
|
tagsCount: {}
|
|
};
|
|
allBookmarks = [];
|
|
};
|
|
|
|
reader.onerror = () => {
|
|
loadingIndicator.style.display = 'none';
|
|
successIndicator.style.display = 'none';
|
|
errorIndicator.textContent = 'Fehler beim Lesen der Datei.';
|
|
errorIndicator.style.display = 'block';
|
|
outputDiv.innerHTML = '';
|
|
statsContainer.style.display = 'none';
|
|
};
|
|
|
|
reader.onload = (e) => {
|
|
loadingIndicator.style.display = 'none';
|
|
try {
|
|
const content = e.target.result;
|
|
|
|
if ((selectedFormat === 'html') ||
|
|
(selectedFormat === 'json' && isHtml)) {
|
|
processHtmlBookmarks(content);
|
|
} else {
|
|
processJsonBookmarks(content);
|
|
}
|
|
|
|
updateStatistics();
|
|
updateSearch();
|
|
|
|
// Erfolgsmeldung anzeigen
|
|
const successIndicator = document.getElementById('successIndicator');
|
|
successIndicator.textContent = `${statistics.totalBookmarks} Lesezeichen und ${statistics.totalFolders} Ordner erfolgreich geladen!`;
|
|
successIndicator.style.display = 'block';
|
|
|
|
// Erfolgsmeldung nach 5 Sekunden ausblenden
|
|
setTimeout(() => {
|
|
successIndicator.style.display = 'none';
|
|
}, 5000);
|
|
|
|
} catch (error) {
|
|
console.error("Fehler beim Parsen:", error);
|
|
errorIndicator.textContent = 'Fehler beim Verarbeiten der Datei: ' + error.message;
|
|
errorIndicator.style.display = 'block';
|
|
outputDiv.innerHTML = '';
|
|
statsContainer.style.display = 'none';
|
|
}
|
|
};
|
|
|
|
reader.readAsText(file);
|
|
});
|
|
|
|
// HTML-Lesezeichen verarbeiten
|
|
function processHtmlBookmarks(content) {
|
|
const parser = new DOMParser();
|
|
const doc = parser.parseFromString(content, 'text/html');
|
|
|
|
// Verbesserte Erkennung der Hauptliste
|
|
let mainDl = null;
|
|
|
|
// Versuche verschiedene gängige Strukturen von Lesezeichendateien zu erkennen
|
|
if (doc.querySelector('dl')) {
|
|
mainDl = doc.querySelector('dl');
|
|
}
|
|
|
|
// Firefox/Chrome/Edge exportieren oft mit diesem Muster
|
|
if (!mainDl && doc.querySelector('html > dl')) {
|
|
mainDl = doc.querySelector('html > dl');
|
|
}
|
|
|
|
// Mozilla/Firefox spezifisches Format
|
|
if (!mainDl && doc.querySelector('H1')) {
|
|
// Prüfen ob es eine Firefox Bookmark Datei ist
|
|
const h1Text = doc.querySelector('H1').textContent;
|
|
if (h1Text && h1Text.includes('Bookmarks')) {
|
|
const bookmarkRoot = doc.querySelector('DL');
|
|
if (bookmarkRoot) {
|
|
mainDl = bookmarkRoot;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Mozilla/Firefox spezifisches Format mit DOCTYPE-Prüfung
|
|
if (!mainDl && content.includes('NETSCAPE-Bookmark-file-1')) {
|
|
const allDLs = doc.querySelectorAll('DL');
|
|
if (allDLs.length > 0) {
|
|
// Wir nehmen die erste DL-Liste
|
|
mainDl = allDLs[0];
|
|
}
|
|
}
|
|
|
|
// Falls immer noch keine DL gefunden wurde
|
|
if (!mainDl) {
|
|
console.error("Konnte die Haupt-<DL>-Liste nicht finden.");
|
|
throw new Error('Ungültige Lesezeichen-Datei-Struktur. Keine <DL>-Liste gefunden.');
|
|
}
|
|
|
|
outputDiv.innerHTML = ''; // Vorherige Inhalte löschen
|
|
parseDl(mainDl, outputDiv); // Parsen von der Hauptliste starten
|
|
updateFolderCounts(); // Aktualisiere die Anzahl der Favoriten in Ordnern
|
|
}
|
|
|
|
// JSON-Lesezeichen verarbeiten
|
|
function processJsonBookmarks(content) {
|
|
const jsonData = JSON.parse(content);
|
|
|
|
outputDiv.innerHTML = ''; // Vorherige Inhalte löschen
|
|
|
|
// Firefox JSON-Struktur verarbeiten
|
|
if (jsonData.children) {
|
|
parseJsonBookmarks(jsonData.children, outputDiv);
|
|
} else if (jsonData.roots) {
|
|
// Ein anderes mögliches Firefox JSON-Format
|
|
parseJsonBookmarks(jsonData.roots, outputDiv);
|
|
} else {
|
|
throw new Error('Unbekanntes JSON-Format. Keine Lesezeichen gefunden.');
|
|
}
|
|
|
|
updateFolderCounts(); // Aktualisiere die Anzahl der Favoriten in Ordnern
|
|
}
|
|
|
|
// JSON-Lesezeichen parsen
|
|
function parseJsonBookmarks(bookmarks, parentContainer) {
|
|
if (Array.isArray(bookmarks)) {
|
|
// Array von Lesezeichen
|
|
bookmarks.forEach(bookmark => {
|
|
processJsonBookmarkNode(bookmark, parentContainer);
|
|
});
|
|
} else if (typeof bookmarks === 'object') {
|
|
// Objekt mit Lesezeichen-Wurzeln wie "menu", "toolbar", etc.
|
|
for (const key in bookmarks) {
|
|
if (bookmarks[key] && typeof bookmarks[key] === 'object') {
|
|
if (bookmarks[key].children) {
|
|
// Dies ist ein Ordner
|
|
const details = document.createElement('details');
|
|
const summary = document.createElement('summary');
|
|
summary.textContent = bookmarks[key].title || key;
|
|
details.appendChild(summary);
|
|
parentContainer.appendChild(details);
|
|
|
|
statistics.totalFolders++;
|
|
|
|
parseJsonBookmarks(bookmarks[key].children, details);
|
|
} else if (bookmarks[key].uri || bookmarks[key].url) {
|
|
// Dies ist ein Lesezeichen
|
|
addBookmarkFromJson(bookmarks[key], parentContainer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Einzelnes JSON-Lesezeichen verarbeiten
|
|
function processJsonBookmarkNode(node, parentContainer) {
|
|
if (!node) return;
|
|
|
|
if (node.type === 'text/x-moz-place-container' ||
|
|
(node.children && !node.uri && !node.url)) {
|
|
// Dies ist ein Ordner
|
|
const details = document.createElement('details');
|
|
const summary = document.createElement('summary');
|
|
summary.textContent = node.title || 'Ordner ohne Namen';
|
|
details.appendChild(summary);
|
|
parentContainer.appendChild(details);
|
|
|
|
statistics.totalFolders++;
|
|
|
|
if (node.children) {
|
|
parseJsonBookmarks(node.children, details);
|
|
}
|
|
} else if (node.type === 'text/x-moz-place' || node.uri || node.url) {
|
|
// Dies ist ein Lesezeichen
|
|
addBookmarkFromJson(node, parentContainer);
|
|
}
|
|
}
|
|
|
|
// JSON-Lesezeichen zur Anzeige hinzufügen
|
|
function addBookmarkFromJson(bookmark, parentContainer) {
|
|
const link = document.createElement('a');
|
|
link.href = bookmark.uri || bookmark.url || '#';
|
|
link.textContent = bookmark.title || link.href;
|
|
link.target = '_blank';
|
|
link.rel = 'noopener noreferrer';
|
|
|
|
statistics.totalBookmarks++;
|
|
|
|
// Domäne extrahieren und zählen
|
|
try {
|
|
const url = new URL(link.href);
|
|
const domain = url.hostname;
|
|
statistics.domains[domain] = (statistics.domains[domain] || 0) + 1;
|
|
} catch (e) {
|
|
console.warn("Ungültige URL:", link.href);
|
|
}
|
|
|
|
// Zusätzliche Metadaten
|
|
const bookmarkData = {
|
|
title: bookmark.title || '',
|
|
url: bookmark.uri || bookmark.url || '',
|
|
dateAdded: bookmark.dateAdded || bookmark.add_date,
|
|
lastModified: bookmark.lastModified,
|
|
tags: bookmark.tags || (bookmark.keyword ? [bookmark.keyword] : [])
|
|
};
|
|
|
|
allBookmarks.push(bookmarkData);
|
|
|
|
// Datum verarbeiten
|
|
if (bookmarkData.dateAdded) {
|
|
let dateObj;
|
|
|
|
// Firefox speichert Datum als Mikrosekunden seit der Epoche
|
|
if (bookmarkData.dateAdded > 10000000000) { // Wahrscheinlich Mikrosekunden
|
|
dateObj = new Date(bookmarkData.dateAdded / 1000);
|
|
} else {
|
|
dateObj = new Date(bookmarkData.dateAdded * 1000); // Sekunden in Millisekunden umwandeln
|
|
}
|
|
|
|
const dateStr = dateObj.toLocaleDateString();
|
|
|
|
if (!statistics.oldestBookmark || dateObj < new Date(statistics.oldestBookmark.date)) {
|
|
statistics.oldestBookmark = { date: dateObj, title: bookmarkData.title };
|
|
}
|
|
|
|
if (!statistics.newestBookmark || dateObj > new Date(statistics.newestBookmark.date)) {
|
|
statistics.newestBookmark = { date: dateObj, title: bookmarkData.title };
|
|
}
|
|
|
|
// Info-Span für das Hinzufügedatum
|
|
const infoSpan = document.createElement('span');
|
|
infoSpan.className = 'bookmark-info';
|
|
infoSpan.textContent = `Hinzugefügt: ${dateStr}`;
|
|
link.appendChild(infoSpan);
|
|
}
|
|
|
|
// Tags verarbeiten
|
|
if (bookmarkData.tags && bookmarkData.tags.length > 0) {
|
|
const tagsDiv = document.createElement('div');
|
|
tagsDiv.className = 'bookmark-tags';
|
|
|
|
bookmarkData.tags.forEach(tag => {
|
|
const tagSpan = document.createElement('span');
|
|
tagSpan.className = 'bookmark-tag';
|
|
tagSpan.textContent = tag;
|
|
tagsDiv.appendChild(tagSpan);
|
|
|
|
// Tag für Statistik zählen
|
|
statistics.tagsCount[tag] = (statistics.tagsCount[tag] || 0) + 1;
|
|
});
|
|
|
|
link.appendChild(tagsDiv);
|
|
}
|
|
|
|
// Icon hinzufügen, falls vorhanden
|
|
if (bookmark.iconUri || bookmark.icon) {
|
|
const icon = document.createElement('img');
|
|
icon.className = 'bookmark-icon';
|
|
icon.src = bookmark.iconUri || bookmark.icon;
|
|
icon.alt = "";
|
|
link.prepend(icon);
|
|
}
|
|
|
|
parentContainer.appendChild(link);
|
|
}
|
|
|
|
function parseDl(dlElement, parentContainer) {
|
|
if (!dlElement || dlElement.tagName !== 'DL') {
|
|
return;
|
|
}
|
|
|
|
// Verarbeite alle Kinder des DL-Elements
|
|
const children = dlElement.children;
|
|
for (let i = 0; i < children.length; i++) {
|
|
let node = children[i];
|
|
|
|
// Handle the optional <p> wrapper in HTML exports
|
|
if (node.tagName === 'P') {
|
|
// Process children of <p>
|
|
for (let j = 0; j < node.children.length; j++) {
|
|
processNode(node.children[j], parentContainer);
|
|
}
|
|
} else {
|
|
processNode(node, parentContainer);
|
|
}
|
|
}
|
|
}
|
|
|
|
function processNode(node, parentContainer) {
|
|
if (!node || !node.tagName) return; // Sicherheitscheck
|
|
|
|
// Verarbeite nur DT-Elemente
|
|
if (node.tagName === 'DT') {
|
|
const h3 = node.querySelector('h3');
|
|
const a = node.querySelector('a');
|
|
|
|
// Find the next DL for this DT (folder)
|
|
let nextDl = null;
|
|
|
|
// Chrome/Firefox-Format: DT mit H3 gefolgt von DL
|
|
if (h3) {
|
|
// Suche nach DL als nächstes Geschwisterelement
|
|
let sibling = node.nextElementSibling;
|
|
while (sibling) {
|
|
if (sibling.tagName === 'DL') {
|
|
nextDl = sibling;
|
|
break;
|
|
}
|
|
sibling = sibling.nextElementSibling;
|
|
}
|
|
|
|
// Manchmal ist DL als Kind-Element in einer verschachtelten Struktur
|
|
if (!nextDl) {
|
|
const parentDl = node.closest('dl');
|
|
if (parentDl) {
|
|
const dlChildren = parentDl.querySelectorAll(':scope > dl');
|
|
if (dlChildren.length > 0) {
|
|
// Versuche das nächste DL zu finden
|
|
for (let i = 0; i < dlChildren.length; i++) {
|
|
if (dlChildren[i].previousElementSibling === node) {
|
|
nextDl = dlChildren[i];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Es könnte auch direkt nach dem H3 als Kind des DT sein
|
|
if (!nextDl && node.querySelector('dl')) {
|
|
nextDl = node.querySelector('dl');
|
|
}
|
|
}
|
|
|
|
if (h3) { // Es ist ein Ordner
|
|
const details = document.createElement('details');
|
|
const summary = document.createElement('summary');
|
|
summary.textContent = h3.textContent.trim() || 'Ordner ohne Namen';
|
|
details.appendChild(summary);
|
|
parentContainer.appendChild(details);
|
|
|
|
statistics.totalFolders++;
|
|
|
|
// Rekursiv den Inhalt des Ordners verarbeiten
|
|
if (nextDl) {
|
|
parseDl(nextDl, details);
|
|
}
|
|
} else if (a) { // Es ist ein Lesezeichen
|
|
const link = document.createElement('a');
|
|
link.href = a.href;
|
|
link.textContent = a.textContent.trim() || a.href;
|
|
link.target = '_blank';
|
|
link.rel = 'noopener noreferrer';
|
|
|
|
statistics.totalBookmarks++;
|
|
|
|
// Domäne extrahieren und zählen
|
|
try {
|
|
const url = new URL(link.href);
|
|
const domain = url.hostname;
|
|
statistics.domains[domain] = (statistics.domains[domain] || 0) + 1;
|
|
} catch (e) {
|
|
console.warn("Ungültige URL:", link.href);
|
|
}
|
|
|
|
// Bookmark für Suchindex hinzufügen
|
|
const bookmarkData = {
|
|
title: link.textContent,
|
|
url: link.href
|
|
};
|
|
|
|
// Zusätzliche Attribute aus dem Original-Element extrahieren
|
|
if (a.hasAttribute('add_date')) {
|
|
const addDate = parseInt(a.getAttribute('add_date'));
|
|
bookmarkData.dateAdded = addDate;
|
|
|
|
const dateObj = new Date(addDate * 1000); // Sekunden in Millisekunden umwandeln
|
|
const dateStr = dateObj.toLocaleDateString();
|
|
|
|
if (!statistics.oldestBookmark || dateObj < new Date(statistics.oldestBookmark.date)) {
|
|
statistics.oldestBookmark = { date: dateObj, title: bookmarkData.title };
|
|
}
|
|
|
|
if (!statistics.newestBookmark || dateObj > new Date(statistics.newestBookmark.date)) {
|
|
statistics.newestBookmark = { date: dateObj, title: bookmarkData.title };
|
|
}
|
|
|
|
// Info-Span für das Hinzufügedatum
|
|
const infoSpan = document.createElement('span');
|
|
infoSpan.className = 'bookmark-info';
|
|
infoSpan.textContent = `Hinzugefügt: ${dateStr}`;
|
|
link.appendChild(infoSpan);
|
|
}
|
|
|
|
// Tags (in Firefox Keyword genannt)
|
|
if (a.hasAttribute('tags') || a.hasAttribute('keyword')) {
|
|
const tags = a.getAttribute('tags') || a.getAttribute('keyword');
|
|
if (tags) {
|
|
bookmarkData.tags = tags.split(',').map(tag => tag.trim());
|
|
|
|
const tagsDiv = document.createElement('div');
|
|
tagsDiv.className = 'bookmark-tags';
|
|
|
|
bookmarkData.tags.forEach(tag => {
|
|
const tagSpan = document.createElement('span');
|
|
tagSpan.className = 'bookmark-tag';
|
|
tagSpan.textContent = tag;
|
|
tagsDiv.appendChild(tagSpan);
|
|
|
|
// Tag für Statistik zählen
|
|
statistics.tagsCount[tag] = (statistics.tagsCount[tag] || 0) + 1;
|
|
});
|
|
|
|
link.appendChild(tagsDiv);
|
|
}
|
|
}
|
|
|
|
allBookmarks.push(bookmarkData);
|
|
|
|
// Wenn ein Icon vorhanden ist, füge es hinzu
|
|
const icon = a.querySelector('img') || (a.getAttribute('icon') ? new Image() : null);
|
|
if (icon) {
|
|
if (a.getAttribute('icon') && !icon.src) {
|
|
icon.src = a.getAttribute('icon');
|
|
}
|
|
icon.className = 'bookmark-icon';
|
|
link.prepend(icon);
|
|
}
|
|
|
|
parentContainer.appendChild(link);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Statistiken aktualisieren
|
|
function updateStatistics() {
|
|
if (statistics.totalBookmarks === 0) {
|
|
statsContainer.style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
statsGrid.innerHTML = '';
|
|
|
|
// Anzahl der Lesezeichen
|
|
addStatItem('Lesezeichen', statistics.totalBookmarks);
|
|
|
|
// Anzahl der Ordner
|
|
addStatItem('Ordner', statistics.totalFolders);
|
|
|
|
// Top-Domänen
|
|
const domainEntries = Object.entries(statistics.domains).sort((a, b) => b[1] - a[1]);
|
|
if (domainEntries.length > 0) {
|
|
const topDomain = domainEntries[0];
|
|
addStatItem('Top-Domain', `${topDomain[0]} (${topDomain[1]})`);
|
|
}
|
|
|
|
// Ältestes Lesezeichen
|
|
if (statistics.oldestBookmark) {
|
|
const dateStr = statistics.oldestBookmark.date.toLocaleDateString();
|
|
addStatItem('Ältestes Lesezeichen', dateStr);
|
|
}
|
|
|
|
// Neuestes Lesezeichen
|
|
if (statistics.newestBookmark) {
|
|
const dateStr = statistics.newestBookmark.date.toLocaleDateString();
|
|
addStatItem('Neuestes Lesezeichen', dateStr);
|
|
}
|
|
|
|
// Häufigste Tags
|
|
const tagEntries = Object.entries(statistics.tagsCount).sort((a, b) => b[1] - a[1]);
|
|
if (tagEntries.length > 0) {
|
|
const topTag = tagEntries[0];
|
|
addStatItem('Häufigster Tag', `${topTag[0]} (${topTag[1]})`);
|
|
}
|
|
|
|
statsContainer.style.display = 'block';
|
|
}
|
|
|
|
// Statistik-Element hinzufügen
|
|
function addStatItem(label, value) {
|
|
const statItem = document.createElement('div');
|
|
statItem.className = 'stat-item';
|
|
|
|
const statValue = document.createElement('div');
|
|
statValue.className = 'stat-value';
|
|
|
|
if (label === 'Top-Domain' && typeof value === 'string') {
|
|
// Für lange Domain-Namen
|
|
const parts = value.split(' (');
|
|
const domain = parts[0];
|
|
const count = parts[1] ? '(' + parts[1] : '';
|
|
|
|
// Domain und Anzahl als separate Elemente
|
|
const domainSpan = document.createElement('span');
|
|
domainSpan.textContent = domain;
|
|
domainSpan.className = 'domain-text';
|
|
|
|
const countSpan = document.createElement('span');
|
|
countSpan.textContent = count;
|
|
countSpan.className = 'domain-count';
|
|
|
|
statValue.appendChild(domainSpan);
|
|
statValue.appendChild(countSpan);
|
|
} else {
|
|
statValue.textContent = value;
|
|
}
|
|
|
|
const statLabel = document.createElement('div');
|
|
statLabel.className = 'stat-label';
|
|
statLabel.textContent = label;
|
|
|
|
statItem.appendChild(statValue);
|
|
statItem.appendChild(statLabel);
|
|
|
|
statsGrid.appendChild(statItem);
|
|
}
|
|
|
|
// Suche initialisieren
|
|
function updateSearch() {
|
|
searchInput.addEventListener('input', performSearch);
|
|
}
|
|
|
|
// Suche durchführen
|
|
function performSearch() {
|
|
const searchTerm = searchInput.value.toLowerCase().trim();
|
|
|
|
if (!searchTerm) {
|
|
// Alle Details-Elemente zurücksetzen
|
|
const allDetails = outputDiv.querySelectorAll('details');
|
|
allDetails.forEach(details => {
|
|
details.removeAttribute('open');
|
|
});
|
|
|
|
// Alle Links anzeigen
|
|
const allLinks = outputDiv.querySelectorAll('a');
|
|
allLinks.forEach(link => {
|
|
link.style.display = 'flex';
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// Lesezeichen durchsuchen
|
|
const allLinks = outputDiv.querySelectorAll('a');
|
|
allLinks.forEach(link => {
|
|
const linkText = link.textContent.toLowerCase();
|
|
const linkHref = link.href.toLowerCase();
|
|
const matchesSearch = linkText.includes(searchTerm) || linkHref.includes(searchTerm);
|
|
|
|
link.style.display = matchesSearch ? 'flex' : 'none';
|
|
|
|
// Wenn ein Link übereinstimmt, öffne alle übergeordneten Details-Elemente
|
|
if (matchesSearch) {
|
|
let parent = link.parentElement;
|
|
while (parent) {
|
|
if (parent.tagName === 'DETAILS') {
|
|
parent.setAttribute('open', '');
|
|
}
|
|
parent = parent.parentElement;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// Funktion zum Aktualisieren der Anzahl der Favoriten in Ordnern
|
|
function updateFolderCounts() {
|
|
const allDetails = outputDiv.querySelectorAll('details');
|
|
|
|
allDetails.forEach(details => {
|
|
// Zähle alle direkten Links (nicht verschachtelt in anderen Details)
|
|
const directLinks = Array.from(details.children).filter(
|
|
child => child.tagName === 'A'
|
|
).length;
|
|
|
|
// Rekursiv alle Links in verschachtelten Details zählen
|
|
const nestedDetails = details.querySelectorAll('details');
|
|
let totalNestedLinks = 0;
|
|
|
|
nestedDetails.forEach(nestedDetail => {
|
|
const nestedLinks = nestedDetail.querySelectorAll('a');
|
|
totalNestedLinks += nestedLinks.length;
|
|
});
|
|
|
|
// Gesamtanzahl der Links
|
|
const totalLinks = directLinks + totalNestedLinks;
|
|
|
|
// Aktualisiere den Summary-Text
|
|
const summary = details.querySelector('summary');
|
|
if (summary) {
|
|
// Prüfe ob bereits eine Anzahl in Klammern hinzugefügt wurde
|
|
const originalText = summary.textContent.replace(/\s*\(\d+\)\s*$/, '').trim();
|
|
summary.textContent = `${originalText} (${totalLinks})`;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Aktuelle Jahreszahl im Footer
|
|
document.getElementById('current-year').textContent = new Date().getFullYear();
|
|
</script>
|
|
</body>
|
|
</html> |