22 Commits

Author SHA1 Message Date
Zan
a94c06d893 Dupla óra 2026-01-15 19:10:30 +01:00
Zan
7cd8c065b3 fix 2026-01-15 17:59:19 +01:00
Zan
d44be3b029 Message attachments 2026-01-15 17:56:40 +01:00
Zan
3243ebdc2e Merge branch 'main' of https://github.com/QwIT-Development/firka-extension 2026-01-15 17:43:44 +01:00
Zan
0f69a1583b Online ora 2026-01-15 17:42:53 +01:00
Zan
15ab433064 Sy removed 2026-01-15 17:23:09 +01:00
Zan
4aab9afebd message html fix 2026-01-15 17:18:19 +01:00
Zan
e0df578dca Update logo image source in README.md 2026-01-15 17:10:59 +01:00
Zan
dd320d3dc4 Online óra 2026-01-15 17:07:59 +01:00
Zan
5248cfc12e Revert "#23 fix"
This reverts commit 1429943dbe.
2026-01-15 16:27:36 +01:00
Zan
1f34cacf25 updater fix 2026-01-12 20:34:48 +01:00
Zan
f35d29f56c fix custom homework 2026-01-12 20:09:41 +01:00
Zan
1429943dbe #23 fix 2026-01-12 19:53:35 +01:00
Zan
2ca1c9949a updater 2026-01-12 19:33:19 +01:00
Zan
1086086552 sdf 2026-01-12 19:18:50 +01:00
Zan
c0b5ed3e46 new ver 2026-01-12 19:09:51 +01:00
Zan
430bed67c2 Absences rework 2026-01-12 19:03:17 +01:00
Zan
f75eff8c09 é 2026-01-12 18:23:25 +01:00
Zan
f52017585f Updates 2025-12-10 22:15:41 +01:00
Zan
a40b5db6f9 , 2025-12-10 16:12:11 +01:00
Zan
f6a52b73de firefox addon store bullshit 2025-12-10 15:51:37 +01:00
Zan
75d6cc4676 Grades fix 2025-12-08 21:57:11 +01:00
37 changed files with 3627 additions and 1820 deletions

View File

@@ -1,5 +1,5 @@
<p align="center">
<img width="150" height="150" alt="firka_logo_128" src="https://github.com/user-attachments/assets/089b37c8-bdb1-48af-93e5-9fc656fe8c15" />
<img width="150" height="150" alt="firka_logo_128" src="https://github.com/QwIT-Development/firka-extension/blob/main/images/firka_logo_128.png?raw=true" />
<h1 align="center">Firka extension</h1>
</p>

View File

@@ -29,131 +29,51 @@ body {
}
.absences-page {
display: grid;
grid-template-columns: 300px 1fr;
grid-template-rows: 1fr;
grid-template-areas:
"sidebar content";
gap: 24px;
min-height: calc(100vh - 200px);
}
.absences-sidebar {
grid-area: sidebar;
display: flex;
flex-direction: column;
gap: 16px;
position: sticky;
top: 24px;
height: fit-content;
gap: 24px;
}
.filter-card {
.stats-section {
background: var(--card-card);
border-radius: 20px;
padding: 24px;
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
}
.filter-header {
.stats-title {
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 12px;
}
.filter-header h2 {
font-size: 18px;
font-weight: 600;
color: var(--text-primary);
margin: 0;
background-color: #00000000 !important;
}
.filter-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 8px;
}
.filter-group label {
display: flex;
align-items: center;
gap: 10px;
color: var(--text-secondary);
font-size: 14px;
font-weight: 500;
}
.filter-group label img {
width: 20px;
height: 20px;
opacity: 0.8;
}
.filter-input {
padding: 12px 16px;
border: 2px solid transparent;
border-radius: 12px;
background: var(--button-secondaryFill);
color: var(--text-primary);
font-family: inherit;
font-size: 14px;
transition: all 0.2s ease;
cursor: pointer;
}
.filter-input:hover {
background: var(--accent-15);
}
.filter-input:focus {
outline: none;
border-color: var(--accent-accent);
background: var(--button-secondaryFill);
}
.stats-section {
grid-area: stats;
background-color: transparent !important;
margin-top: 0px !important;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
grid-template-columns: repeat(6, 1fr);
gap: 12px;
}
.stat-card {
background: var(--card-card);
background: var(--button-secondaryFill);
border-radius: 16px;
padding: 20px;
padding: 20px 16px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
transition: transform 0.2s ease, box-shadow 0.2s ease;
min-height: 100px;
border: 2px solid transparent;
}
.stat-card:hover {
transform: translateY(-2px);
}
.stat-icon {
width: 24px;
height: 24px;
margin-bottom: 8px;
opacity: 0.8;
}
.stat-number {
font-size: 32px;
font-weight: 700;
@@ -166,18 +86,46 @@ body {
color: var(--accent-accent);
}
.stat-card.total {
border-color: var(--accent-15);
}
.stat-card.absence .stat-number {
color: var(--accent-accent);
}
.stat-card.late .stat-number {
color: var(--grades-2);
}
.stat-card.late {
border-color: var(--grades-background-2);
}
.stat-card.justified .stat-number {
color: var(--grades-4);
}
.stat-card.justified {
border-color: var(--grades-background-4);
}
.stat-card.unjustified .stat-number {
color: var(--grades-1);
}
.stat-card.unjustified {
border-color: var(--grades-background-1);
}
.stat-card.pending .stat-number {
color: var(--grades-3);
}
.stat-card.pending {
border-color: var(--grades-background-3);
}
.stat-label {
color: var(--text-secondary);
font-size: 12px;
@@ -186,163 +134,369 @@ body {
letter-spacing: 0.5px;
}
.absences-content {
grid-area: content;
.calendar-legend {
display: flex;
flex-direction: column;
gap: 24px;
flex-wrap: wrap;
gap: 16px;
padding: 16px 24px;
background: var(--card-card);
border-radius: 16px;
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
}
.day-group {
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-color {
width: 24px;
height: 24px;
border-radius: 8px;
}
.legend-color.status-justified {
background: var(--grades-background-4);
border: 2px solid var(--grades-4);
}
.legend-color.status-pending {
background: var(--grades-background-3);
border: 2px solid var(--grades-3);
}
.legend-color.status-unjustified {
background: var(--grades-background-1);
border: 2px solid var(--grades-1);
}
.legend-color.type-late-legend {
background: var(--grades-background-2);
border: 2px dashed var(--grades-2);
}
.legend-item span {
font-size: 13px;
color: var(--text-secondary);
font-weight: 500;
}
.calendar-section {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 20px;
}
.month-container {
background: var(--card-card);
border-radius: 20px;
overflow: hidden;
box-shadow: 0px 1px var(--shadow-blur) 0px var(--accent-shadow);
}
.day-header {
.month-header {
background: var(--button-secondaryFill);
color: white;
padding: 16px 20px;
font-size: 16px;
font-weight: 600;
text-align: center;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 4px;
padding: 12px;
}
.calendar-day-header {
text-align: center;
font-size: 12px;
font-weight: 600;
color: var(--text-secondary);
padding: 8px 0;
text-transform: uppercase;
}
.calendar-day {
aspect-ratio: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 10px;
background: var(--button-secondaryFill);
position: relative;
min-height: 40px;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.calendar-day.empty {
background: transparent;
}
.calendar-day.future {
background: var(--accent-15);
opacity: 0.5;
}
.calendar-day.future .day-number {
color: var(--text-teritary);
}
.calendar-day.today .day-number {
color: var(--accent-accent);
font-weight: 700;
}
.calendar-day .day-number {
font-size: 14px;
font-weight: 500;
color: var(--text-primary);
z-index: 1;
}
.calendar-day.has-absence {
cursor: pointer;
}
.calendar-day.has-absence:hover {
transform: scale(1.1);
box-shadow: 0 4px 12px var(--accent-shadow);
z-index: 2;
}
.calendar-day.status-justified {
background: var(--grades-background-4);
border: 2px solid var(--grades-4);
}
.calendar-day.status-justified .day-number {
color: var(--grades-4);
font-weight: 700;
}
.calendar-day.status-pending {
background: var(--grades-background-3);
border: 2px solid var(--grades-3);
}
.calendar-day.status-pending .day-number {
color: var(--grades-3);
font-weight: 700;
}
.calendar-day.status-unjustified {
background: var(--grades-background-1);
border: 2px solid var(--grades-1);
}
.calendar-day.status-unjustified .day-number {
color: var(--grades-1);
font-weight: 700;
}
.calendar-day.type-late {
border-style: dashed;
}
.calendar-day.type-mixed {
border-style: double;
border-width: 3px;
}
.absence-count {
position: absolute;
top: 2px;
right: 2px;
background: var(--accent-accent);
color: white;
padding: 16px 24px;
font-size: 9px;
font-weight: 700;
width: 16px;
height: 16px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.calendar-day.status-justified .absence-count {
background: var(--grades-4);
}
.calendar-day.status-pending .absence-count {
background: var(--grades-3);
}
.calendar-day.status-unjustified .absence-count {
background: var(--grades-1);
}
.absence-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
opacity: 0;
transition: opacity 0.2s ease;
padding: 20px;
}
.absence-modal-overlay.visible {
opacity: 1;
}
.absence-modal {
background: var(--card-card);
border-radius: 24px;
max-width: 500px;
width: 100%;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
transform: scale(0.95);
transition: transform 0.2s ease;
}
.absence-modal-overlay.visible .absence-modal {
transform: scale(1);
}
.modal-header {
background: var(--button-secondaryFill);
border-bottom: 1px solid #00000000 !important;
color: white;
padding: 14px 18px !important;
display: flex;
align-items: center;
justify-content: space-between;
font-weight: 600;
}
.day-date {
display: flex;
align-items: center;
gap: 12px;
.modal-header h2 {
font-size: 16px;
font-weight: 600;
margin: 0;
background: transparent !important;
}
.day-date img {
width: 20px;
height: 20px;
filter: brightness(0) invert(1);
.modal-content {
background-color: var(--card-card) !important;
padding: 16px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 8px;
}
.day-count {
background: rgba(255, 255, 255, 0.2);
padding: 4px 12px;
border-radius: 20px;
font-size: 13px;
}
.day-absences {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
gap: 16px;
padding: 20px;
}
.absence-card {
.modal-absence-card {
background: var(--button-secondaryFill);
border-radius: 16px;
padding: 20px;
display: grid;
grid-template-columns: auto 1fr minmax(auto, max-content);
grid-template-rows: auto auto;
grid-template-areas:
"lesson subject status"
"lesson topic status";
gap: 8px 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: 1px solid var(--accent-15);
border-radius: 12px;
padding: 12px;
display: flex;
flex-direction: column;
gap: 6px;
border-left: 3px solid transparent;
}
.absence-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--accent-shadow);
}
.absence-lesson {
grid-area: lesson;
.modal-card-header {
display: flex;
align-items: center;
justify-content: center;
width: 48px;
height: 48px;
background: var(--accent-15);
border-radius: 12px;
font-size: 20px;
gap: 8px;
flex-wrap: wrap;
}
.modal-lesson {
font-size: 16px;
font-weight: 700;
color: var(--accent-accent);
min-width: 24px;
}
.type-indicator {
display: inline-flex;
align-items: center;
padding: 3px 8px 1px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
}
.type-indicator.absence {
background: var(--accent-15);
color: var(--accent-accent);
}
.absence-subject {
grid-area: subject;
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
align-self: end;
.type-indicator.late {
background: var(--grades-background-2);
color: var(--grades-2);
}
.absence-topic {
grid-area: topic;
font-size: 13px;
color: var(--text-secondary);
align-self: start;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.absence-status {
grid-area: status;
display: flex;
align-items: center;
justify-content: center;
}
.status-badge {
.modal-status-badge {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 14px;
border-radius: 24px;
font-size: 11px;
padding: 3px 8px;
border-radius: 12px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.2px;
white-space: nowrap;
flex-shrink: 0;
margin-left: auto;
}
.status-badge img {
width: 16px;
height: 16px;
}
.status-badge.justified {
.modal-status-badge.justified {
background: var(--grades-background-4);
color: var(--grades-4);
}
.status-badge.justified img {
filter: brightness(0) saturate(100%) invert(76%) sepia(43%) saturate(609%) hue-rotate(53deg) brightness(101%) contrast(92%);
}
.status-badge.unjustified {
background: var(--grades-background-1);
color: var(--grades-1);
}
.status-badge.unjustified .material-icons-round {
font-size: 16px;
}
.status-badge.pending {
.modal-status-badge.pending {
background: var(--grades-background-3);
color: var(--grades-3);
}
.status-badge.pending .material-icons-round {
font-size: 16px;
.modal-status-badge.unjustified {
background: var(--grades-background-1);
color: var(--grades-1);
}
.modal-subject {
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
line-height: 1.3;
}
.modal-info-row {
display: flex;
flex-wrap: wrap;
gap: 6px;
font-size: 12px;
color: var(--text-secondary);
}
.modal-info-item {
display: inline-block;
}
.modal-info-item:not(:last-child)::after {
content: '•';
margin-left: 6px;
color: var(--text-teritary);
}
.modal-minutes {
color: var(--grades-2);
font-weight: 600;
}
.empty-state {
@@ -375,14 +529,9 @@ body {
color: var(--text-secondary);
}
@media (max-width: 1024px) {
.absences-page {
grid-template-columns: 260px 1fr;
gap: 20px;
}
.day-absences {
grid-template-columns: 1fr;
@media (max-width: 1200px) {
.stats-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@@ -395,41 +544,18 @@ body {
padding: 16px;
}
.absences-page {
grid-template-columns: 1fr;
grid-template-rows: auto 1fr;
grid-template-areas:
"sidebar"
"content";
gap: 16px;
}
.absences-sidebar {
position: static;
}
.filter-card {
.stats-section {
padding: 20px;
border-radius: 16px;
}
.filter-content {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.filter-group label {
font-size: 12px;
}
.filter-input {
padding: 10px 12px;
font-size: 13px;
.stats-title {
font-size: 18px;
margin-bottom: 16px;
}
.stats-grid {
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
@@ -447,33 +573,78 @@ body {
font-size: 10px;
}
.day-group {
.calendar-section {
grid-template-columns: 1fr;
}
.calendar-legend {
gap: 12px;
padding: 14px 18px;
}
.legend-item span {
font-size: 12px;
}
.month-container {
border-radius: 16px;
}
.day-header {
padding: 14px 18px;
.month-header {
padding: 14px 16px;
font-size: 15px;
}
.calendar-grid {
gap: 3px;
padding: 10px;
}
.calendar-day {
min-height: 36px;
border-radius: 8px;
}
.calendar-day .day-number {
font-size: 13px;
}
.absence-count {
width: 14px;
height: 14px;
font-size: 8px;
}
.absence-modal {
max-width: 100%;
max-height: 90vh;
border-radius: 20px;
}
.modal-header {
padding: 12px 16px;
}
.modal-header h2 {
font-size: 15px;
}
.modal-content {
padding: 12px;
gap: 6px;
}
.modal-absence-card {
padding: 10px;
gap: 5px;
}
.modal-subject {
font-size: 14px;
}
.day-absences {
padding: 14px;
gap: 12px;
}
.absence-card {
padding: 16px;
border-radius: 12px;
}
.absence-lesson {
width: 44px;
height: 44px;
font-size: 18px;
}
.absence-subject {
font-size: 15px;
.modal-info-row {
font-size: 11px;
}
}
@@ -483,26 +654,13 @@ body {
}
.absences-page {
gap: 12px;
gap: 16px;
}
.filter-card {
.stats-section {
padding: 16px;
}
.filter-header {
margin-bottom: 16px;
}
.filter-header h2 {
font-size: 16px;
}
.filter-content {
grid-template-columns: 1fr;
gap: 14px;
}
.stats-grid {
grid-template-columns: repeat(2, 1fr);
gap: 8px;
@@ -523,63 +681,35 @@ body {
letter-spacing: 0.3px;
}
.day-header {
padding: 12px 16px;
flex-wrap: wrap;
gap: 8px;
}
.day-date {
font-size: 14px;
}
.day-count {
font-size: 12px;
padding: 3px 10px;
}
.day-absences {
padding: 12px;
.calendar-legend {
flex-direction: column;
gap: 10px;
}
.absence-card {
padding: 14px;
grid-template-columns: auto 1fr;
grid-template-rows: auto auto auto;
grid-template-areas:
"lesson subject"
"lesson topic"
"status status";
gap: 6px 12px;
.calendar-grid {
gap: 2px;
padding: 8px;
}
.absence-lesson {
width: 40px;
height: 40px;
font-size: 16px;
grid-row: span 2;
.calendar-day {
min-height: 32px;
}
.absence-status {
justify-content: flex-start;
margin-top: 8px;
padding-top: 10px;
border-top: 1px solid var(--accent-15);
}
.status-badge {
padding: 6px 12px;
font-size: 11px;
}
.absence-subject {
font-size: 14px;
}
.absence-topic {
.calendar-day .day-number {
font-size: 12px;
}
.absence-modal-overlay {
padding: 12px;
}
.modal-absence-card {
padding: 14px;
}
.modal-subject {
font-size: 16px;
}
}
@keyframes fadeIn {
@@ -593,17 +723,15 @@ body {
}
}
.day-group {
.month-container {
animation: fadeIn 0.3s ease forwards;
}
.day-group:nth-child(1) { animation-delay: 0.05s; }
.day-group:nth-child(2) { animation-delay: 0.1s; }
.day-group:nth-child(3) { animation-delay: 0.15s; }
.day-group:nth-child(4) { animation-delay: 0.2s; }
.day-group:nth-child(5) { animation-delay: 0.25s; }
.month-container:nth-child(1) { animation-delay: 0.05s; }
.month-container:nth-child(2) { animation-delay: 0.1s; }
.month-container:nth-child(3) { animation-delay: 0.15s; }
.month-container:nth-child(4) { animation-delay: 0.2s; }
.month-container:nth-child(5) { animation-delay: 0.25s; }
.loading-overlay {
position: fixed;

View File

@@ -15,7 +15,7 @@ async function collectAbsencesData() {
try {
const currentDomain = window.location.hostname;
const response = await fetch(
`https://${currentDomain}/api/HianyzasokApi/GetHianyzasGrid?sort=MulasztasDatum-desc&page=1&pageSize=100&group=&filter=&data=%7B%7D&_=${Date.now()}`,
`https://${currentDomain}/api/HianyzasokApi/GetHianyzasGrid?sort=MulasztasDatum-desc&page=1&pageSize=500&group=&filter=&data=%7B%7D&_=${Date.now()}`,
{
method: "GET",
credentials: "include",
@@ -45,530 +45,449 @@ async function collectAbsencesData() {
justificationStatus = "unjustified";
}
const absenceType = item.MulasztasTipus === 1500 ? 'absence' : item.MulasztasTipus === 1499 ? 'late' : 'other';
absences.push({
date: formattedDate,
rawDate: date,
dateKey: `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, "0")}-${date.getDate().toString().padStart(2, "0")}`,
lesson: item.Oraszam?.toString() || "",
subject: item.Targy || "",
topic: item.Tema || "",
type: item.MulasztasTipus_DNAME || "",
absenceType: absenceType,
absenceTypeId: item.MulasztasTipus,
justified: item.Igazolt_BOOL === true,
justificationStatus: justificationStatus,
purposeful: item.TanoraiCeluMulasztas_BNAME || "",
justificationType: item.IgazolasTipus_DNAME || "",
minutes: item.KesesPercben || 0,
});
});
}
const groupedAbsences = absences.reduce((groups, absence) => {
const date = absence.date;
if (!groups[date]) {
groups[date] = [];
const groupedByDate = absences.reduce((groups, absence) => {
const key = absence.dateKey;
if (!groups[key]) {
groups[key] = [];
}
groups[date].push(absence);
groups[key].push(absence);
return groups;
}, {});
Object.keys(groupedAbsences).forEach(date => {
groupedAbsences[date].sort((a, b) => parseInt(a.lesson) - parseInt(b.lesson));
Object.keys(groupedByDate).forEach(date => {
groupedByDate[date].sort((a, b) => parseInt(a.lesson) - parseInt(b.lesson));
});
return { basicData, absences, groupedAbsences };
return { basicData, absences, groupedByDate };
} catch (error) {
console.error("Hiba az API hívás során:", error);
return { basicData, absences: [], groupedAbsences: {} };
return { basicData, absences: [], groupedByDate: {} };
}
}
function createFilterCard(absences) {
const filterCard = document.createElement('div');
filterCard.className = 'filter-card';
const filterHeader = document.createElement('div');
filterHeader.className = 'filter-header';
const h2 = document.createElement('h2');
h2.textContent = LanguageManager.t('absences.filter_title');
filterHeader.appendChild(h2);
const filterContent = document.createElement('div');
filterContent.className = 'filter-content';
const dateGroup = document.createElement('div');
dateGroup.className = 'filter-group';
const dateLabel = document.createElement('label');
const dateImg = document.createElement('img');
dateImg.src = chrome.runtime.getURL('icons/Calendar.svg');
dateImg.alt = 'Dátum';
dateLabel.appendChild(dateImg);
dateLabel.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.date')));
const dateInput = document.createElement('input');
dateInput.type = 'date';
dateInput.id = 'dateFilter';
dateInput.className = 'filter-input';
dateGroup.appendChild(dateLabel);
dateGroup.appendChild(dateInput);
filterContent.appendChild(dateGroup);
const subjectGroup = document.createElement('div');
subjectGroup.className = 'filter-group';
const subjectLabel = document.createElement('label');
const subjectImg = document.createElement('img');
subjectImg.src = chrome.runtime.getURL('icons/Subject.svg');
subjectImg.alt = 'Tantárgy';
subjectLabel.appendChild(subjectImg);
subjectLabel.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.subject')));
const subjectSelect = document.createElement('select');
subjectSelect.id = 'subjectFilter';
subjectSelect.className = 'filter-input';
const defaultSubjectOption = document.createElement('option');
defaultSubjectOption.value = '';
defaultSubjectOption.textContent = LanguageManager.t('absences.all_subjects');
subjectSelect.appendChild(defaultSubjectOption);
const subjects = [...new Set(absences.map(a => a.subject))].sort();
subjects.forEach(subject => {
const option = document.createElement('option');
option.value = subject;
option.textContent = subject;
subjectSelect.appendChild(option);
});
subjectGroup.appendChild(subjectLabel);
subjectGroup.appendChild(subjectSelect);
filterContent.appendChild(subjectGroup);
const justificationGroup = document.createElement('div');
justificationGroup.className = 'filter-group';
const justificationLabel = document.createElement('label');
const justificationImg = document.createElement('img');
justificationImg.src = chrome.runtime.getURL('icons/BadgeCheck.svg');
justificationImg.alt = 'Igazolás';
justificationLabel.appendChild(justificationImg);
justificationLabel.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.justification')));
const justificationSelect = document.createElement('select');
justificationSelect.id = 'justificationFilter';
justificationSelect.className = 'filter-input';
const justificationOptions = [
{ value: '', text: LanguageManager.t('absences.all_types') },
{ value: 'justified', text: LanguageManager.t('absences.justified') },
{ value: 'unjustified', text: LanguageManager.t('absences.unjustified') },
{ value: 'pending', text: LanguageManager.t('absences.pending') }
];
justificationOptions.forEach(optionData => {
const option = document.createElement('option');
option.value = optionData.value;
option.textContent = optionData.text;
justificationSelect.appendChild(option);
});
justificationGroup.appendChild(justificationLabel);
justificationGroup.appendChild(justificationSelect);
filterContent.appendChild(justificationGroup);
filterCard.appendChild(filterHeader);
filterCard.appendChild(filterContent);
return filterCard;
}
function createStatsSection(absences) {
const statsSection = document.createElement('div');
statsSection.className = 'stats-section';
const statsTitle = document.createElement('h2');
statsTitle.className = 'stats-title';
statsTitle.textContent = LanguageManager.t('absences.title');
statsSection.appendChild(statsTitle);
const statsGrid = document.createElement('div');
statsGrid.className = 'stats-grid';
const totalAbsences = absences.filter(a => a.absenceType === 'absence').length;
const totalLates = absences.filter(a => a.absenceType === 'late').length;
const justified = absences.filter(a => a.justificationStatus === 'justified').length;
const unjustified = absences.filter(a => a.justificationStatus === 'unjustified').length;
const pending = absences.filter(a => a.justificationStatus === 'pending').length;
const stats = [
{
type: 'total',
number: absences.length,
label: LanguageManager.t('absences.total_absences')
},
{
type: 'justified',
number: absences.filter(a => a.justificationStatus === 'justified').length,
label: LanguageManager.t('absences.justified')
},
{
type: 'unjustified',
number: absences.filter(a => a.justificationStatus === 'unjustified').length,
label: LanguageManager.t('absences.unjustified')
},
{
type: 'pending',
number: absences.filter(a => a.justificationStatus === 'pending').length,
label: LanguageManager.t('absences.pending')
}
{ type: 'total', number: absences.length, label: LanguageManager.t('absences.total_absences') },
{ type: 'absence', number: totalAbsences, label: LanguageManager.t('absences.absence_type') },
{ type: 'late', number: totalLates, label: LanguageManager.t('absences.late_type') },
{ type: 'justified', number: justified, label: LanguageManager.t('absences.justified') },
{ type: 'unjustified', number: unjustified, label: LanguageManager.t('absences.unjustified') },
{ type: 'pending', number: pending, label: LanguageManager.t('absences.pending') },
];
stats.forEach(stat => {
const statCard = document.createElement('div');
statCard.className = `stat-card ${stat.type}`;
statCard.dataset.type = stat.type;
const statNumber = document.createElement('div');
statNumber.className = 'stat-number';
statNumber.textContent = stat.number;
const statLabel = document.createElement('div');
statLabel.className = 'stat-label';
statLabel.textContent = stat.label;
statCard.appendChild(statNumber);
statCard.appendChild(statLabel);
statsGrid.appendChild(statCard);
});
statsSection.appendChild(statsGrid);
return statsSection;
}
function createDayGroup(date, dayAbsences) {
const dayGroup = document.createElement('div');
dayGroup.className = 'day-group';
dayGroup.dataset.date = date;
const dayHeader = document.createElement('div');
dayHeader.className = 'day-header';
const dayDate = document.createElement('div');
dayDate.className = 'day-date';
const calendarIcon = document.createElement('img');
calendarIcon.src = chrome.runtime.getURL('icons/Calendar.svg');
calendarIcon.alt = 'Dátum';
const dateText = document.createElement('span');
dateText.textContent = formatDateWithDay(date);
dayDate.appendChild(calendarIcon);
dayDate.appendChild(dateText);
const dayCount = document.createElement('div');
dayCount.className = 'day-count';
dayCount.textContent = `${dayAbsences.length} ${LanguageManager.t('absences.hours')}`;
dayHeader.appendChild(dayDate);
dayHeader.appendChild(dayCount);
const dayAbsencesContainer = document.createElement('div');
dayAbsencesContainer.className = 'day-absences';
dayAbsences.forEach(absence => {
const absenceCard = createAbsenceCard(absence);
dayAbsencesContainer.appendChild(absenceCard);
});
dayGroup.appendChild(dayHeader);
dayGroup.appendChild(dayAbsencesContainer);
return dayGroup;
function getSchoolYearStart() {
const now = new Date();
const year = now.getMonth() >= 8 ? now.getFullYear() : now.getFullYear() - 1;
return new Date(year, 8, 1);
}
function formatDateWithDay(dateStr) {
const parts = dateStr.split('.');
const year = parseInt(parts[0]);
const month = parseInt(parts[1]) - 1;
const day = parseInt(parts[2]);
const date = new Date(year, month, day);
const days = [
LanguageManager.t('common.sunday'),
LanguageManager.t('common.monday'),
LanguageManager.t('common.tuesday'),
LanguageManager.t('common.wednesday'),
LanguageManager.t('common.thursday'),
LanguageManager.t('common.friday'),
LanguageManager.t('common.saturday')
];
const dayName = days[date.getDay()];
return `${dateStr} - ${dayName.charAt(0).toUpperCase() + dayName.slice(1)}`;
}
function createCalendarSection(groupedByDate, absences) {
const calendarSection = document.createElement('div');
calendarSection.className = 'calendar-section';
function createAbsenceCard(absence) {
const card = document.createElement('div');
card.className = 'absence-card';
card.dataset.subject = absence.subject;
card.dataset.status = absence.justificationStatus;
card.dataset.date = absence.date;
const schoolYearStart = getSchoolYearStart();
const now = new Date();
const lessonDiv = document.createElement('div');
lessonDiv.className = 'absence-lesson';
lessonDiv.textContent = absence.lesson + '.';
const months = [];
let currentDate = new Date(schoolYearStart);
const subjectDiv = document.createElement('div');
subjectDiv.className = 'absence-subject';
subjectDiv.textContent = absence.subject;
const topicDiv = document.createElement('div');
topicDiv.className = 'absence-topic';
topicDiv.textContent = absence.topic || '-';
topicDiv.title = absence.topic;
const statusDiv = document.createElement('div');
statusDiv.className = 'absence-status';
const statusBadge = document.createElement('span');
statusBadge.className = `status-badge ${absence.justificationStatus}`;
if (absence.justificationStatus === 'justified') {
const img = document.createElement('img');
img.src = chrome.runtime.getURL('icons/BadgeCheck.svg');
img.alt = 'Igazolt';
statusBadge.appendChild(img);
statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.justified')));
} else if (absence.justificationStatus === 'unjustified') {
const span = document.createElement('span');
span.className = 'material-icons-round';
span.textContent = 'cancel';
statusBadge.appendChild(span);
statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.unjustified')));
} else {
const img = document.createElement('img');
img.src = chrome.runtime.getURL('icons/pending.svg');
img.alt = 'Függőben';
statusBadge.appendChild(img);
statusBadge.appendChild(document.createTextNode(' ' + LanguageManager.t('absences.pending')));
}
statusDiv.appendChild(statusBadge);
card.appendChild(lessonDiv);
card.appendChild(subjectDiv);
card.appendChild(topicDiv);
card.appendChild(statusDiv);
return card;
}
function createAbsencesContent(groupedAbsences) {
const content = document.createElement('div');
content.className = 'absences-content';
const sortedDates = Object.keys(groupedAbsences).sort((a, b) => {
const dateA = new Date(a.replace(/\./g, '-').slice(0, -1));
const dateB = new Date(b.replace(/\./g, '-').slice(0, -1));
return dateB - dateA;
});
if (sortedDates.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'empty-state';
const emptyIcon = document.createElement('img');
emptyIcon.src = chrome.runtime.getURL('icons/BadgeCheck.svg');
emptyIcon.alt = 'Nincs hiányzás';
const emptyTitle = document.createElement('h3');
emptyTitle.textContent = LanguageManager.t('absences.title');
const emptyText = document.createElement('p');
emptyText.textContent = LanguageManager.t('dashboard.not_supported');
emptyState.appendChild(emptyIcon);
emptyState.appendChild(emptyTitle);
emptyState.appendChild(emptyText);
content.appendChild(emptyState);
} else {
sortedDates.forEach(date => {
const dayGroup = createDayGroup(date, groupedAbsences[date]);
content.appendChild(dayGroup);
while (currentDate <= now) {
months.push({
year: currentDate.getFullYear(),
month: currentDate.getMonth()
});
currentDate.setMonth(currentDate.getMonth() + 1);
}
return content;
months.forEach(({ year, month }) => {
const monthCalendar = createMonthCalendar(year, month, groupedByDate, absences);
calendarSection.appendChild(monthCalendar);
});
return calendarSection;
}
function createMonthCalendar(year, month, groupedByDate, absences) {
const monthContainer = document.createElement('div');
monthContainer.className = 'month-container';
const monthHeader = document.createElement('div');
monthHeader.className = 'month-header';
const monthNames = [
LanguageManager.t('common.january') || 'Január',
LanguageManager.t('common.february') || 'Február',
LanguageManager.t('common.march') || 'Március',
LanguageManager.t('common.april') || 'Április',
LanguageManager.t('common.may') || 'Május',
LanguageManager.t('common.june') || 'Június',
LanguageManager.t('common.july') || 'Július',
LanguageManager.t('common.august') || 'Augusztus',
LanguageManager.t('common.september') || 'Szeptember',
LanguageManager.t('common.october') || 'Október',
LanguageManager.t('common.november') || 'November',
LanguageManager.t('common.december') || 'December'
];
monthHeader.textContent = `${monthNames[month]} ${year}`;
monthContainer.appendChild(monthHeader);
const calendarGrid = document.createElement('div');
calendarGrid.className = 'calendar-grid';
const dayNames = [
LanguageManager.t('common.mon') || 'H',
LanguageManager.t('common.tue') || 'K',
LanguageManager.t('common.wed') || 'Sze',
LanguageManager.t('common.thu') || 'Cs',
LanguageManager.t('common.fri') || 'P',
LanguageManager.t('common.sat') || 'Szo',
LanguageManager.t('common.sun') || 'V'
];
dayNames.forEach(day => {
const dayHeader = document.createElement('div');
dayHeader.className = 'calendar-day-header';
dayHeader.textContent = day;
calendarGrid.appendChild(dayHeader);
});
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
const daysInMonth = lastDay.getDate();
let startDay = firstDay.getDay() - 1;
if (startDay < 0) startDay = 6;
for (let i = 0; i < startDay; i++) {
const emptyCell = document.createElement('div');
emptyCell.className = 'calendar-day empty';
calendarGrid.appendChild(emptyCell);
}
const today = new Date();
today.setHours(0, 0, 0, 0);
for (let day = 1; day <= daysInMonth; day++) {
const dateKey = `${year}-${(month + 1).toString().padStart(2, "0")}-${day.toString().padStart(2, "0")}`;
const dayAbsences = groupedByDate[dateKey] || [];
const currentDayDate = new Date(year, month, day);
currentDayDate.setHours(0, 0, 0, 0);
const dayCell = document.createElement('div');
dayCell.className = 'calendar-day';
if (currentDayDate > today) {
dayCell.classList.add('future');
}
if (currentDayDate.getTime() === today.getTime()) {
dayCell.classList.add('today');
}
if (dayAbsences.length > 0) {
dayCell.classList.add('has-absence');
const hasUnjustified = dayAbsences.some(a => a.justificationStatus === 'unjustified');
const hasPending = dayAbsences.some(a => a.justificationStatus === 'pending');
const hasJustified = dayAbsences.some(a => a.justificationStatus === 'justified');
const hasAbsence = dayAbsences.some(a => a.absenceType === 'absence');
const hasLate = dayAbsences.some(a => a.absenceType === 'late');
if (hasUnjustified) {
dayCell.classList.add('status-unjustified');
} else if (hasPending) {
dayCell.classList.add('status-pending');
} else if (hasJustified) {
dayCell.classList.add('status-justified');
}
if (hasAbsence && hasLate) {
dayCell.classList.add('type-mixed');
} else if (hasLate) {
dayCell.classList.add('type-late');
}
dayCell.addEventListener('click', () => {
openAbsenceModal(dateKey, dayAbsences);
});
const countBadge = document.createElement('span');
countBadge.className = 'absence-count';
countBadge.textContent = dayAbsences.length;
dayCell.appendChild(countBadge);
}
const dayNumber = document.createElement('span');
dayNumber.className = 'day-number';
dayNumber.textContent = day;
dayCell.appendChild(dayNumber);
calendarGrid.appendChild(dayCell);
}
monthContainer.appendChild(calendarGrid);
return monthContainer;
}
function openAbsenceModal(dateKey, dayAbsences) {
const existingModal = document.querySelector('.absence-modal-overlay');
if (existingModal) {
existingModal.remove();
}
const overlay = document.createElement('div');
overlay.className = 'absence-modal-overlay';
const modal = document.createElement('div');
modal.className = 'absence-modal';
const modalHeader = document.createElement('div');
modalHeader.className = 'modal-header';
const dateParts = dateKey.split('-');
const displayDate = new Date(parseInt(dateParts[0]), parseInt(dateParts[1]) - 1, parseInt(dateParts[2]));
const dayNames = [
LanguageManager.t('common.sunday') || 'Vasárnap',
LanguageManager.t('common.monday') || 'Hétfő',
LanguageManager.t('common.tuesday') || 'Kedd',
LanguageManager.t('common.wednesday') || 'Szerda',
LanguageManager.t('common.thursday') || 'Csütörtök',
LanguageManager.t('common.friday') || 'Péntek',
LanguageManager.t('common.saturday') || 'Szombat'
];
const modalTitle = document.createElement('h2');
modalTitle.textContent = `${dateParts[0]}.${dateParts[1]}.${dateParts[2]}. - ${dayNames[displayDate.getDay()]}`;
modalHeader.appendChild(modalTitle);
const modalContent = document.createElement('div');
modalContent.className = 'modal-content';
dayAbsences.forEach(absence => {
const absenceCard = document.createElement('div');
absenceCard.className = `modal-absence-card ${absence.justificationStatus}`;
const headerRow = document.createElement('div');
headerRow.className = 'modal-card-header';
const lessonSpan = document.createElement('span');
lessonSpan.className = 'modal-lesson';
lessonSpan.textContent = `${absence.lesson}.`;
const typeIndicator = document.createElement('span');
typeIndicator.className = `type-indicator ${absence.absenceType}`;
typeIndicator.textContent = absence.absenceType === 'late'
? (LanguageManager.t('absences.late_type') || 'Késés')
: (LanguageManager.t('absences.absence_type') || 'Hiányzás');
const statusBadge = document.createElement('span');
statusBadge.className = `modal-status-badge ${absence.justificationStatus}`;
let statusText = '';
if (absence.justificationStatus === 'justified') {
statusText = LanguageManager.t('absences.justified') || 'Igazolt';
} else if (absence.justificationStatus === 'unjustified') {
statusText = LanguageManager.t('absences.unjustified') || 'Igazolatlan';
} else {
statusText = LanguageManager.t('absences.pending') || 'Igazolásra vár';
}
statusBadge.textContent = statusText;
headerRow.appendChild(lessonSpan);
headerRow.appendChild(typeIndicator);
headerRow.appendChild(statusBadge);
const subjectDiv = document.createElement('div');
subjectDiv.className = 'modal-subject';
subjectDiv.textContent = absence.subject;
const infoRow = document.createElement('div');
infoRow.className = 'modal-info-row';
if (absence.topic) {
const topicSpan = document.createElement('span');
topicSpan.className = 'modal-info-item';
topicSpan.textContent = absence.topic;
infoRow.appendChild(topicSpan);
}
if (absence.justificationType) {
const justificationSpan = document.createElement('span');
justificationSpan.className = 'modal-info-item';
justificationSpan.textContent = absence.justificationType;
infoRow.appendChild(justificationSpan);
}
if (absence.absenceType === 'late' && absence.minutes > 0) {
const minutesSpan = document.createElement('span');
minutesSpan.className = 'modal-info-item modal-minutes';
minutesSpan.textContent = `${absence.minutes} ${LanguageManager.t('absences.minutes') || 'perc'}`;
infoRow.appendChild(minutesSpan);
}
absenceCard.appendChild(headerRow);
absenceCard.appendChild(subjectDiv);
if (infoRow.children.length > 0) {
absenceCard.appendChild(infoRow);
}
modalContent.appendChild(absenceCard);
});
modal.appendChild(modalHeader);
modal.appendChild(modalContent);
overlay.appendChild(modal);
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
overlay.remove();
}
});
const handleEscape = (e) => {
if (e.key === 'Escape') {
overlay.remove();
document.removeEventListener('keydown', handleEscape);
}
};
document.addEventListener('keydown', handleEscape);
document.body.appendChild(overlay);
requestAnimationFrame(() => {
overlay.classList.add('visible');
});
}
function createLegend() {
const legend = document.createElement('div');
legend.className = 'calendar-legend';
const legendItems = [
{ class: 'status-justified', label: LanguageManager.t('absences.justified') || 'Igazolt' },
{ class: 'status-pending', label: LanguageManager.t('absences.pending') || 'Igazolásra vár' },
{ class: 'status-unjustified', label: LanguageManager.t('absences.unjustified') || 'Igazolatlan' },
{ class: 'type-late-legend', label: LanguageManager.t('absences.late_type') || 'Késés', isType: true },
];
legendItems.forEach(item => {
const legendItem = document.createElement('div');
legendItem.className = 'legend-item';
const legendColor = document.createElement('div');
legendColor.className = `legend-color ${item.class}`;
const legendLabel = document.createElement('span');
legendLabel.textContent = item.label;
legendItem.appendChild(legendColor);
legendItem.appendChild(legendLabel);
legend.appendChild(legendItem);
});
return legend;
}
async function transformAbsencesPage() {
const { basicData, absences, groupedAbsences } = await collectAbsencesData();
const { basicData, absences, groupedByDate } = await collectAbsencesData();
document.body.textContent = '';
const container = document.createElement('div');
container.className = 'kreta-container';
const headerDiv = document.createElement('div');
const parser = new DOMParser();
const doc = parser.parseFromString(await createTemplate.header(), 'text/html');
const tempDiv = doc.body;
const template = document.createElement('template');
template.innerHTML = await createTemplate.header();
const tempDiv = template.content;
while (tempDiv.firstChild) {
headerDiv.appendChild(tempDiv.firstChild);
}
container.appendChild(headerDiv);
const main = document.createElement('main');
main.className = 'kreta-main';
const pageGrid = document.createElement('div');
pageGrid.className = 'absences-page';
const sidebar = document.createElement('div');
sidebar.className = 'absences-sidebar';
const filterCard = createFilterCard(absences);
sidebar.appendChild(filterCard);
const pageContent = document.createElement('div');
pageContent.className = 'absences-page';
const statsSection = createStatsSection(absences);
sidebar.appendChild(statsSection);
const absencesContent = createAbsencesContent(groupedAbsences);
pageGrid.appendChild(sidebar);
pageGrid.appendChild(absencesContent);
main.appendChild(pageGrid);
pageContent.appendChild(statsSection);
const legend = createLegend();
pageContent.appendChild(legend);
const calendarSection = createCalendarSection(groupedByDate, absences);
pageContent.appendChild(calendarSection);
main.appendChild(pageContent);
container.appendChild(main);
document.body.appendChild(container);
setupUserDropdown();
setupFilters(groupedAbsences);
loadingScreen.hide();
}
function setupFilters(originalGroupedAbsences) {
try {
const dateFilter = document.getElementById("dateFilter");
const subjectFilter = document.getElementById("subjectFilter");
const justificationFilter = document.getElementById("justificationFilter");
if (!dateFilter || !subjectFilter || !justificationFilter) {
console.warn("Some filter elements were not found in the DOM");
return;
}
const filterAbsences = () => {
try {
const dateFilterValue = dateFilter.value;
const subject = subjectFilter.value;
const justified = justificationFilter.value;
const selectedDate = dateFilterValue ? new Date(dateFilterValue) : null;
document.querySelectorAll(".absence-card").forEach((card) => {
const dateStr = card.dataset.date;
const dateParts = dateStr.split(".");
if (dateParts.length < 3) {
return;
}
const parsedYear = parseInt(dateParts[0].trim(), 10);
const parsedMonth = parseInt(dateParts[1].trim(), 10) - 1;
const parsedDay = parseInt(dateParts[2].trim(), 10);
const cardDate = new Date(parsedYear, parsedMonth, parsedDay);
let showCard = true;
if (selectedDate) {
if (
cardDate.getFullYear() !== selectedDate.getFullYear() ||
cardDate.getMonth() !== selectedDate.getMonth() ||
cardDate.getDate() !== selectedDate.getDate()
) {
showCard = false;
}
}
if (subject && card.dataset.subject !== subject) {
showCard = false;
}
if (justified && card.dataset.status !== justified) {
showCard = false;
}
card.style.display = showCard ? "" : "none";
});
updateDayGroupsVisibility();
updateStatistics();
} catch (err) {
console.error("Error during filtering absences:", err);
}
};
[dateFilter, subjectFilter, justificationFilter].forEach((filter) => {
if (filter) {
filter.addEventListener("change", filterAbsences);
}
});
} catch (err) {
console.error("Error setting up filters:", err);
}
}
function updateDayGroupsVisibility() {
document.querySelectorAll(".day-group").forEach((group) => {
const visibleCards = group.querySelectorAll('.absence-card:not([style*="display: none"])');
const dayCount = group.querySelector('.day-count');
if (visibleCards.length > 0) {
group.style.display = "";
if (dayCount) {
dayCount.textContent = `${visibleCards.length} ${LanguageManager.t('absences.hours')}`;
}
} else {
group.style.display = "none";
}
});
}
function updateStatistics() {
try {
const visibleCards = document.querySelectorAll('.absence-card:not([style*="display: none"])');
const totalVisible = visibleCards.length;
const justifiedVisible = Array.from(visibleCards).filter(
card => card.dataset.status === 'justified'
).length;
const unjustifiedVisible = Array.from(visibleCards).filter(
card => card.dataset.status === 'unjustified'
).length;
const pendingVisible = Array.from(visibleCards).filter(
card => card.dataset.status === 'pending'
).length;
const statCards = document.querySelectorAll(".stat-card");
statCards.forEach(card => {
const type = card.dataset.type;
const numberEl = card.querySelector('.stat-number');
if (numberEl) {
switch(type) {
case 'total':
numberEl.textContent = totalVisible;
break;
case 'justified':
numberEl.textContent = justifiedVisible;
break;
case 'unjustified':
numberEl.textContent = unjustifiedVisible;
break;
case 'pending':
numberEl.textContent = pendingVisible;
break;
}
}
});
} catch (err) {
console.error("Error updating statistics:", err);
}
}
if (window.location.href.includes("/Hianyzas/Hianyzasok")) {
transformAbsencesPage().catch((error) => {
console.error(LanguageManager.t("absences.page_transform_error"), error);

View File

@@ -499,22 +499,22 @@ class DashboardRenderer {
async render() {
await this.init();
document.body.innerHTML = '';
helper.clearElement(document.body);
const kretaContainer = document.createElement('div');
kretaContainer.className = 'kreta-container';
const headerDiv = document.createElement('div');
const parser = new DOMParser();
const headerDoc = parser.parseFromString(await createTemplate.header(), 'text/html');
const headerContent = headerDoc.body;
const template = document.createElement('template');
template.innerHTML = await createTemplate.header();
const headerContent = template.content;
while (headerContent.firstChild) {
headerDiv.appendChild(headerContent.firstChild);
}
kretaContainer.appendChild(headerDiv);
const mainContentDiv = document.createElement('div');
const parser2 = new DOMParser();
const mainDoc = parser2.parseFromString(this.generateMainContent(), 'text/html');
const mainContent = mainDoc.body;
const template2 = document.createElement('template');
template2.innerHTML = this.generateMainContent();
const mainContent = template2.content;
while (mainContent.firstChild) {
mainContentDiv.appendChild(mainContent.firstChild);
}

View File

@@ -1,57 +0,0 @@
body.maintenance-mode {
margin:0;
padding:0;
height:100vh;
display:flex;
align-items:center;
justify-content:center;
background-color:var(--background);
color:var(--text-primary);
font-family:'Figtree',sans-serif;
}
body {
background-color:var(--background) !important;
}
.maintenance-container {
text-align:center;
padding:2rem;
border-radius:8px;
background-color:var(--card-card);
box-shadow:0 var(--shadow-blur) 6px var(--accent-shadow);
max-width:600px;
width:90%;
}
.maintenance-logo {
width:128px;
height:128px;
margin:0 auto 2rem;
}
.maintenance-title {
font-size:1.5rem;
font-weight:600;
margin-bottom:1rem;
color:var(--accent-accent);
font-family:'Montserrat',sans-serif;
}
.maintenance-message {
font-size:1rem;
line-height:1.5;
margin-bottom:1.5rem;
color:var(--text-primary);
font-family:'Figtree',sans-serif;
}
.maintenance-footer {
font-size:0.875rem;
color:var(--text-secondary);
margin-top:2rem;
font-family:'Figtree',sans-serif;
}
.maintenance-cactus {
position:fixed;
bottom:0px;
right:20px;
width:120px;
height:120px;
opacity:1;
z-index:1000;
}

View File

@@ -1,290 +0,0 @@
(() => {
// reCAPTCHA functionality removed for security compliance
const loadDependencies = async () => {
// reCAPTCHA functionality removed for security compliance
// Extension now works without external script dependencies
};
const createPageStructure = () => {
// Biztonságos DOM létrehozás innerHTML helyett
document.body.innerHTML = '';
// Biztonságos HTML parsing DOMParser használatával
const parser = new DOMParser();
const doc = parser.parseFromString(`
<div class="forgot-container">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,100..900;1,100..900&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons+Round" rel="stylesheet">
<style>
.g-recaptcha {
margin-top: 5px;
display: flex;
justify-content: center;
}
</style>
<div class="forgot-card">
<header class="forgot-header">
<p class="logo-text">
<img src="${chrome.runtime.getURL("images/firka_logo.png")}" alt="Firka" class="logo">
Firka
</p>
</header>
<h1 class="forgot-title" data-i18n="forgotpassword.title">Elfelejtett jelszó</h1>
<form id="forgotForm" novalidate>
<div class="form-group">
<label class="form-label" for="BejelentkezesiNev" data-i18n="forgotpassword.om_id_label">OM azonosító</label>
<input type="text" id="BejelentkezesiNev" name="BejelentkezesiNev" class="form-control"
data-i18n-attr="placeholder" data-i18n="forgotpassword.om_id_placeholder"
placeholder="Adja meg az OM azonosítóját" required>
<div class="error-message" data-i18n="forgotpassword.om_id_required">Az OM azonosító megadása kötelező</div>
</div>
<div class="form-group">
<label class="form-label" for="EmailCim" data-i18n="forgotpassword.email_label">E-mail cím</label>
<input type="email" id="EmailCim" name="EmailCim" class="form-control"
data-i18n-attr="placeholder" data-i18n="forgotpassword.email_placeholder"
placeholder="Adja meg az e-mail címét" required>
<div class="error-message" data-i18n="forgotpassword.email_required">Az e-mail cím megadása kötelező</div>
</div>
<!-- reCAPTCHA container removed for security compliance -->
<div class="form-actions">
<a href="/Adminisztracio/Login" class="help-link" data-i18n="forgotpassword.back_to_login">
Vissza a bejelentkezéshez
</a>
<button type="submit" class="btn-submit" data-i18n="forgotpassword.reset_button">
Jelszó visszaállítása
</button>
</div>
</form>
</div>
</div>
`, 'text/html');
const tempDiv = doc.body;
// Biztonságos DOM hozzáadás
while (tempDiv.firstChild) {
document.body.appendChild(tempDiv.firstChild);
}
};
const transformForgotPasswordPage = async () => {
await loadDependencies();
const isDarkMode = localStorage.getItem("darkMode") === "true";
document.documentElement.setAttribute(
"data-theme",
isDarkMode ? "dark" : "light",
);
chrome.runtime.onMessage.addListener((message) => {
if (message.action === "toggleTheme") {
document.documentElement.setAttribute(
"data-theme",
message.darkMode ? "dark" : "light",
);
localStorage.setItem("darkMode", message.darkMode);
}
});
createPageStructure();
let attempts = 0;
const maxAttempts = 50;
const waitForLanguageManager = () => {
return new Promise((resolve) => {
const checkLanguageManager = () => {
attempts++;
if (typeof LanguageManager !== "undefined" && LanguageManager.t) {
setTimeout(resolve, 200);
} else if (attempts < maxAttempts) {
setTimeout(checkLanguageManager, 100);
} else {
console.warn("LanguageManager not available, using fallback texts");
resolve();
}
};
checkLanguageManager();
});
};
await waitForLanguageManager();
if (typeof LanguageManager !== "undefined" && LanguageManager.t) {
const elements = document.querySelectorAll("[data-i18n]");
elements.forEach((element) => {
const key = element.getAttribute("data-i18n");
const translation = LanguageManager.t(key);
if (translation && translation !== key) {
const attr = element.getAttribute("data-i18n-attr");
if (attr) {
element.setAttribute(attr, translation);
} else {
element.textContent = translation;
}
}
});
}
// reCAPTCHA rendering removed for security compliance
setupFormValidation();
};
const setupFormValidation = () => {
const form = document.getElementById("forgotForm");
const inputs = form.querySelectorAll(".form-control");
inputs.forEach((input) => {
input.addEventListener("input", () => {
validateInput(input);
});
input.addEventListener("blur", () => {
validateInput(input, true);
});
});
form.addEventListener("submit", handleSubmit);
};
const validateInput = (input, showError = false) => {
const isValid = input.value.trim().length > 0;
const errorElement = input.nextElementSibling;
if (!isValid && showError) {
input.classList.add("error");
errorElement?.classList.add("show");
} else {
input.classList.remove("error");
errorElement?.classList.remove("show");
}
return isValid;
};
const showMessage = (message, isError = false) => {
const existingMessage = document.querySelector(".message");
if (existingMessage) {
existingMessage.remove();
}
const messageDiv = document.createElement("div");
messageDiv.className = `message ${isError ? "error" : "success"}`;
messageDiv.textContent = message;
const form = document.getElementById("forgotForm");
form.insertBefore(messageDiv, form.firstChild);
setTimeout(() => {
if (messageDiv.parentNode) {
messageDiv.remove();
}
}, 5000);
};
const validateEmail = (email) => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};
const handleSubmit = async (event) => {
event.preventDefault();
const form = event.target;
const inputs = form.querySelectorAll(".form-control[required]");
let isValid = true;
inputs.forEach((input) => {
if (!validateInput(input, true)) {
isValid = false;
}
});
const emailInput = form.querySelector("#EmailCim");
if (emailInput.value && !validateEmail(emailInput.value)) {
emailInput.classList.add("error");
const errorElement = emailInput.nextElementSibling;
if (errorElement) {
errorElement.textContent = LanguageManager.t(
"forgotpassword.invalid_email",
);
errorElement.classList.add("show");
}
isValid = false;
}
// reCAPTCHA validation removed for security compliance
if (!isValid) {
return;
}
const submitButton = form.querySelector(".btn-submit");
const originalText = submitButton.textContent;
submitButton.disabled = true;
submitButton.textContent = LanguageManager.t("loading.text") || "Küldés...";
try {
const formData = new FormData(form);
// reCAPTCHA data removed for security compliance
const response = await fetch(
"/Adminisztracio/ElfelejtettJelszo/LinkKuldes",
{
method: "POST",
body: formData,
headers: {
"X-Requested-With": "XMLHttpRequest",
},
},
);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.Success) {
showMessage(LanguageManager.t("forgotpassword.success_message"));
form.reset();
// reCAPTCHA reset removed
setTimeout(() => {
window.location.href = "/Adminisztracio/Login";
}, 3000);
} else {
showMessage(
result.Message || LanguageManager.t("forgotpassword.error_message"),
true,
);
// reCAPTCHA reset removed
}
} catch (error) {
console.error("Password reset error:", error);
showMessage(LanguageManager.t("forgotpassword.error_message"), true);
// reCAPTCHA reset removed
} finally {
submitButton.disabled = false;
submitButton.textContent = originalText;
}
};
if (window.location.href.includes("/Adminisztracio/ElfelejtettJelszo")) {
transformForgotPasswordPage().catch(console.error);
}
})();

View File

@@ -184,6 +184,7 @@
getAvailableLanguages: () => [
{ code: "hu", name: "Magyar" },
{ code: "en", name: "English" },
{ code: "de", name: "Deutsch" },
],
};
})();

View File

@@ -45,7 +45,7 @@ function checkMaintenancePage() {
);
existingStyles.forEach((style) => style.remove());
body.innerHTML = "";
helper.clearElement(body);
body.classList.add("maintenance-mode");
body.classList.add("theme-enabled");
body.classList.add("loaded");

View File

@@ -14,15 +14,13 @@
function applyCustomThemeColors(theme) {
const root = document.documentElement;
const isDark = theme.mode === "dark";
root.style.setProperty("--background", theme.colors.background);
root.style.setProperty("--background-0", theme.colors.background + "00");
root.style.setProperty("--card-card", theme.colors.card);
root.style.setProperty("--card-translucent", theme.colors.card + "80");
root.style.setProperty("--accent-accent", theme.colors.accent);
root.style.setProperty("--text-primary", theme.colors.text);
// Származtatott színek
root.style.setProperty("--text-secondary", theme.colors.text + "cc");
root.style.setProperty("--text-teritary", theme.colors.text + "80");
root.style.setProperty("--accent-15", theme.colors.accent + "26");
@@ -30,19 +28,22 @@
root.style.setProperty("--accent-secondary", isDark ? lightenColor(theme.colors.accent, 20) : darkenColor(theme.colors.accent, 20));
root.style.setProperty("--shadow-blur", isDark ? "0" : "2px");
root.style.setProperty("--accent-shadow", isDark ? "#0000" : theme.colors.accent + "26");
// SVG ikon filter beállítása a kiemelő szín alapján
root.style.setProperty("--warning-accent", "#FFA046");
root.style.setProperty("--warning-text", isDark ? "#f0b37a" : "#8F531B");
root.style.setProperty("--warning-15", "#ffa04626");
root.style.setProperty("--warning-card", isDark ? "#201203" : "#FAEBDC");
root.style.setProperty("--error-accent", "#FF54A1");
root.style.setProperty("--error-text", isDark ? "#f59ec5" : "#8F1B4F");
root.style.setProperty("--error-15", "#FF54A126");
root.style.setProperty("--error-card", isDark ? "#1e030f" : "#FADCE9");
root.style.setProperty("--icon-filter", hexToFilter(theme.colors.accent));
}
// Hex szín átalakítása CSS filterré
function hexToFilter(hex) {
// Hex -> RGB
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
// RGB -> HSL
const rNorm = r / 255;
const gNorm = g / 255;
const bNorm = b / 255;
@@ -66,10 +67,6 @@
const hue = Math.round(h * 360);
const saturation = Math.round(s * 100);
const lightness = Math.round(l * 100);
// Filter létrehozása
// Ez egy egyszerűsített megközelítés - a pontos szín reprodukálásához
// komplexebb számítás kellene, de ez megfelelő a legtöbb esetben
const brightnessVal = lightness > 50 ? 1 + (lightness - 50) / 100 : 0.5 + lightness / 100;
const saturateVal = saturation > 0 ? 1 + saturation / 100 : 0;
@@ -100,20 +97,20 @@
"--background", "--background-0", "--card-card", "--card-translucent",
"--accent-accent", "--text-primary", "--text-secondary", "--text-teritary",
"--accent-15", "--button-secondaryFill", "--accent-secondary",
"--shadow-blur", "--accent-shadow", "--icon-filter"
"--shadow-blur", "--accent-shadow", "--icon-filter",
"--warning-accent", "--warning-text", "--warning-15", "--warning-card",
"--error-accent", "--error-text", "--error-15", "--error-card"
];
customProps.forEach(prop => root.style.removeProperty(prop));
}
async function setTheme(theme) {
try {
// Töröljük az előző egyéni téma stílusait
clearCustomThemeStyles();
document.documentElement.setAttribute("data-theme", theme);
await storageManager.set("themePreference", theme);
// Ha egyéni téma, alkalmazzuk a színeket
if (theme.startsWith("custom-")) {
await loadCustomThemes();
const themeId = theme.replace("custom-", "");
@@ -244,7 +241,6 @@
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "changeTheme") {
// Ha egyéni témák is jöttek az üzenetben, frissítsük a lokális listát
if (message.customThemes) {
customThemes = message.customThemes;
}

View File

@@ -28,17 +28,18 @@
const classAverage = calculateOverallClassAverage(gradesData.subjects);
window.currentGradesData = gradesData;
document.body.innerHTML = '';
const parser = new DOMParser();
const doc = parser.parseFromString(await generatePageHTML(
const htmlString = await generatePageHTML(
gradesData,
studentAverage,
classAverage,
), 'text/html');
const tempDiv = doc.body;
while (tempDiv.firstChild) {
document.body.appendChild(tempDiv.firstChild);
}
);
const template = document.createElement('template');
template.innerHTML = htmlString;
helper.clearElement(document.body);
document.body.appendChild(template.content);
setupUserDropdown();
@@ -65,9 +66,9 @@
async function fetchGradesFromAPI(tanuloId) {
try {
const currentDomain = window.location.origin;
const apiUrl = `${currentDomain}/api/TanuloErtekelesByTanuloApi/GetTanuloErtekelesByTanuloGridTanuloView?sort=&group=&filter=&data=%7B%22tanuloId%22%3A%22${tanuloId}%22%2C%22oktatasiNevelesiFeladatId%22%3A%227895%22%2C%22isOsztalyAtlagMegjelenik%22%3A%22True%22%7D&_=${Date.now()}`;
let apiUrl = `${currentDomain}/api/TanuloErtekelesByTanuloApi/GetTanuloErtekelesByTanuloGridTanuloView?sort=&group=&filter=&data=%7B%22tanuloId%22%3A%22${tanuloId}%22%2C%22oktatasiNevelesiFeladatId%22%3A%227895%22%2C%22isOsztalyAtlagMegjelenik%22%3A%22True%22%7D&_=${Date.now()}`;
const response = await fetch(apiUrl, {
let response = await fetch(apiUrl, {
method: "GET",
credentials: "include",
headers: {
@@ -80,7 +81,28 @@
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
let data = await response.json();
if (data.Data && Array.isArray(data.Data) && data.Data.length === 1 && data.Data[0].ID === 0) {
console.log("Received only ID: 0, retrying with alternative endpoint...");
apiUrl = `${currentDomain}/api/TanuloErtekelesByTanuloApi/GetTanuloErtekelesByTanuloGridTanuloView?sort=&group=&filter=&data=%7B%22tanuloId%22%3A%22${tanuloId}%22%2C%22oktatasiNevelesiFeladatId%22%3A%221160%22%2C%22isOsztalyAtlagMegjelenik%22%3A%22True%22%7D&_=${Date.now()}`;
response = await fetch(apiUrl, {
method: "GET",
credentials: "include",
headers: {
Accept: "application/json, text/javascript, */*; q=0.01",
"X-Requested-With": "XMLHttpRequest",
},
});
if (!response.ok) {
throw new Error(`HTTP error on retry! status: ${response.status}`);
}
data = await response.json();
}
return await processAPIGradesData(data);
} catch (error) {
console.error("Error fetching grades from API:", error);

559
i18n/de.json Normal file
View File

@@ -0,0 +1,559 @@
{
"loading": {
"text": "Laden...",
"subtext": "Bitte warten!"
},
"settings": {
"title": "Einstellungen",
"appearance": "Erscheinungsbild",
"theme": "Design",
"language": "Sprache",
"tabs": {
"home": "Startseite",
"appearance": "Erscheinungsbild",
"settings": "Einstellungen",
"about": "Über"
},
"page_settings": {
"title": "Seiteneinstellungen",
"current_page": "Aktuelle Seite",
"no_settings": "Keine benutzerdefinierten Einstellungen für diese Seite verfügbar.",
"login": {
"hide_system_message": "Systemmeldung ausblenden",
"hide_system_message_desc": "Systemmeldung auf der Anmeldeseite ausblenden",
"hide_school_info": "Schulname und ID ausblenden",
"hide_school_info_desc": "Der Schulname und die KRÉTA-ID werden nicht angezeigt"
},
"roleselect": {
"auto_redirect": "Automatische Weiterleitung",
"auto_redirect_desc": "Automatische Weiterleitung zum Notenbuch",
"hide_school_info": "Schule und Name ausblenden",
"hide_school_info_desc": "Der Schulname und Benutzername werden nicht angezeigt"
},
"bulletin": {
"hide_grades": "Noten ausblenden",
"hide_grades_desc": "Die Notenkarte ausblenden",
"hide_absences": "Fehlzeiten ausblenden",
"hide_absences_desc": "Die Fehlzeitenkarte ausblenden",
"hide_notes": "Notizen ausblenden",
"hide_notes_desc": "Die Notizenkarte ausblenden",
"hide_exams": "Angekündigte Prüfungen ausblenden",
"hide_exams_desc": "Die Karte für angekündigte Prüfungen ausblenden"
},
"grades": {
"hide_chart": "Diagramm ausblenden",
"hide_chart_desc": "Das Notendiagramm ausblenden",
"hide_class_average": "Klassendurchschnitt ausblenden",
"hide_class_average_desc": "Klassendurchschnittswerte ausblenden"
}
},
"themes": {
"light_green": "Hellgrün",
"dark_green": "Dunkelgrün",
"dark_red": "Dunkelrot",
"dark_purple": "Dunkellila",
"dark_orange": "Dunkelorange",
"dark_pink": "Dunkelrosa",
"dark_yellow": "Dunkelgelb",
"dark_cyan": "Dunkelcyan",
"dark_lime": "Dunkellime",
"dark_indigo": "Dunkelindigo"
},
"custom_themes": {
"title": "Benutzerdefinierte Designs",
"no_themes": "Noch keine benutzerdefinierten Designs",
"create": "Neues Design erstellen",
"edit": "Design bearbeiten",
"name": "Designname",
"mode": "Modus",
"dark_mode": "Dunkel",
"light_mode": "Hell",
"colors": "Farben",
"accent_color": "Akzentfarbe",
"background_color": "Hintergrundfarbe",
"card_color": "Kartenfarbe",
"text_color": "Textfarbe",
"preview": "Vorschau",
"share": "Design teilen",
"share_description": "Kopieren Sie den Code und teilen Sie ihn mit anderen:",
"import": "Design importieren",
"import_description": "Fügen Sie den Design-Code ein:",
"import_error_empty": "Bitte fügen Sie den Design-Code ein!",
"import_error_invalid": "Ungültiger Design-Code!",
"delete_confirm": "Möchten Sie dieses Design wirklich löschen?",
"manage": "Verwalten"
},
"languages": {
"hu": "Magyar",
"en": "English",
"de": "Deutsch"
},
"about": {
"title": "Über",
"description": "Firka ist ein Open-Source-Projekt, das eine benutzerdefinierte Benutzeroberfläche für das KRÉTA-System erstellt.",
"github": "GitHub"
},
"support": {
"title": "Unterstützung",
"description": "Wenn Ihnen unsere Arbeit gefällt und Sie die Entwicklung unterstützen möchten, können Sie dies folgendermaßen tun:",
"kofi": "Ko-Fi"
},
"error_reporting": {
"title": "Fehlerberichterstattung",
"enable": "Fehlerberichterstattung aktivieren",
"enable_desc": "Automatische Fehlerberichte an Entwickler senden, um die Erweiterung zu verbessern",
"report_issue": "Fehler oder Idee melden",
"report_issue_desc": "Öffnen Sie ein GitHub-Issue"
}
},
"navigation": {
"dashboard": "Dashboard",
"timetable": "Stundenplan",
"grades": "Noten",
"homework": "Hausaufgaben",
"absences": "Fehlzeiten",
"messages": "Nachrichten",
"profile": "Profil",
"settings": "Einstellungen",
"logout": "Abmelden",
"nav_toggle": "Navigation öffnen"
},
"dashboard": {
"welcome": "Willkommen",
"recent_grades": "Letzte Noten",
"upcoming_lessons": "Kommende Stunden",
"homework_due": "Fällige Hausaufgaben",
"news": "Nachrichten",
"grades": "Ihre Bewertungen",
"absences": "Fehlzeiten",
"notes": "Notizen",
"exams": "Angekündigte Prüfungen",
"all_news": "Alle Nachrichten",
"all_grades": "Alle Noten",
"all_absences": "Alle Fehlzeiten",
"all_messages": "Alle Nachrichten",
"all_exams": "Alle Prüfungen",
"not_supported": "Derzeit sind keine Daten zum Laden verfügbar",
"evaluation": "Bewertung"
},
"grades": {
"title": "Noten",
"subject": "Fach",
"grade": "Note",
"date": "Datum",
"teacher": "Lehrer",
"average": "Durchschnitt",
"chart_title": "Noten",
"semester_evaluation": "Semesterbewertung",
"semester_evaluations": "Semesterbewertungen",
"year_end_evaluations": "Jahresabschlussbewertungen",
"semester_average": "Semesterdurchschnitt",
"no_grades": "Keine Noten",
"september": "September",
"october": "Oktober",
"november": "November",
"december": "Dezember",
"january_1": "JanuarI",
"january_2": "JanuarII",
"february": "Februar",
"march": "März",
"april": "April",
"may": "Mai",
"june_1": "JuniI",
"june_2": "JuniII"
},
"timetable": {
"title": "Stundenplan",
"lesson": "Stunde",
"time": "Zeit",
"subject": "Fach",
"teacher": "Lehrer",
"classroom": "Klassenzimmer",
"homework_indicator": "Hausaufgaben",
"test_indicator": "Prüfung",
"teacher_label": "Lehrer:",
"substitute_teacher_label": "Vertretungslehrer:",
"classroom_label": "Raum:",
"time_label": "Zeit:",
"status_label": "Status:",
"substitution": "Vertretung",
"cancelled": "Abgesagt",
"has_homework": "Hat Hausaufgaben",
"no_lessons_this_week": "Keine Stunden in dieser Woche oder Zeitüberschreitung",
"monday": "Montag",
"tuesday": "Dienstag",
"wednesday": "Mittwoch",
"thursday": "Donnerstag",
"friday": "Freitag",
"found_current_week": "Aktuelle Woche gefunden",
"open_homework": "Zu den Hausaufgaben gehen",
"all_day": "Ganztägig",
"special_day": "Besonderer Tag",
"unknown_subject": "Unbekanntes Fach",
"lesson_topic": "Stundenthema",
"homework_completed": "Hausaufgabe erledigt",
"homework_mark_completed": "Als erledigt markieren",
"homework_mark_uncompleted": "Erledigt - Klicken zum Rückgängigmachen",
"custom_homework": "Eigene Hausaufgaben",
"custom_test": "Eigene Prüfung",
"add_homework_test": "Hausaufgaben oder Prüfung hinzufügen",
"close": "Schließen",
"add": "Hinzufügen",
"homework_details_loading": "Hausaufgabendetails werden geladen...",
"homework_details_error": "Fehler beim Laden der Hausaufgabendetails.",
"test_details_loading": "Details werden geladen...",
"test_details_error": "Prüfungsdetails konnten nicht geladen werden.",
"test_details_error_general": "Fehler beim Laden der Prüfungsdetails.",
"custom_homework_title": "Eigene Hausaufgaben:",
"custom_tests_title": "Eigene Prüfungen:",
"delete_homework_confirm": "Möchten Sie diese Hausaufgabe wirklich löschen?",
"delete_test_confirm": "Möchten Sie diese Prüfung wirklich löschen?",
"task_label": "Aufgabe:",
"deadline_label": "Frist:",
"name_label": "Name:",
"type_label": "Typ:",
"announce_date_label": "Ankündigungsdatum:",
"no_name": "Kein Name",
"no_type": "Kein Typ angegeben",
"no_date": "Kein Datum"
},
"homework": {
"title": "Hausaufgaben",
"due_date": "Fälligkeitsdatum",
"subject": "Fach",
"description": "Beschreibung",
"filter_title": "Filter",
"all_subjects": "Alle Fächer",
"all_teachers": "Alle Lehrer",
"all_deadlines": "Alle Fristen",
"tomorrow_deadline": "Frist morgen",
"this_week": "Diese Woche",
"next_week": "Nächste Woche",
"no_homework": "Keine anzeigbaren Hausaufgaben.",
"no_filtered_homework": "Keine Hausaufgaben, die den Filterkriterien entsprechen.",
"teacher": "Lehrer",
"no_matching_homework": "Keine Hausaufgaben, die den Filterkriterien entsprechen.",
"items": "Element",
"status": "Status",
"total_homework": "Alle Aufgaben",
"urgent_homework": "Dringende Aufgaben",
"completed_homework": "Erledigte Aufgaben",
"pending_homework": "Ausstehende Aufgaben",
"completed": "Abgeschlossen",
"urgent": "Dringend",
"pending": "Ausstehend"
},
"absences": {
"title": "Fehlzeiten",
"date": "Datum",
"lesson": "Stunde",
"type": "Typ",
"justified": "Entschuldigt",
"unjustified": "Unentschuldigt",
"filter_title": "Filter",
"all_subjects": "Alle Fächer",
"all_types": "Alle",
"pending": "Wartet auf Entschuldigung",
"subject": "Fach",
"justification": "Entschuldigung",
"hours": "Stunden",
"page_transform_error": "Fehler bei der Seitentransformation",
"time_period": "Zeitraum",
"all_periods": "Alle Zeiträume",
"current_month": "Aktueller Monat",
"last_month": "Letzter Monat",
"current_semester": "Aktuelles Semester",
"last_30_days": "Letzte 30 Tage",
"total_absences": "Alle Fehlzeiten",
"topic": "Thema",
"status": "Status",
"absence_type": "Abwesenheit",
"late_type": "Verspätung",
"minutes": "Minuten"
},
"profile": {
"title": "Profil",
"name": "Name",
"class": "Klasse",
"school": "Schule",
"student_id": "Schüler-ID",
"settings_title": "Profileinstellungen",
"tab_settings": "Einstellungen",
"tab_password": "Passwort ändern",
"tab_security": "Sicherheitseinstellungen",
"tab_contacts": "Kontakte",
"two_factor_description": "Um die Zwei-Faktor-Authentifizierung zu verwenden, installieren Sie eine zeitbasierte Einmalpasswort-App (TOTP):",
"android": "Android",
"iphone": "iPhone",
"enable_2fa": "Zwei-Faktor-Authentifizierung aktivieren",
"security_key": "Sicherheitsschlüssel:",
"verification_code_label": "Bestätigungscode",
"verification_code_help": "Geben Sie den 6-stelligen Code aus der Authentifizierungs-App ein.",
"verification_code_placeholder": "123456",
"verify_and_activate": "Überprüfen und aktivieren",
"backup_codes_description": "Sie können die folgenden Sicherheitscodes zum Anmelden verwenden, wenn Sie keinen Zugriff auf Ihre Authentifizierungs-App haben. Jeder Code kann nur einmal verwendet werden.",
"email_label": "E-Mail-Adresse",
"email_help": "Die E-Mail-Adresse ist für die Passwort-Erinnerung erforderlich.",
"phone_label": "Telefonnummer",
"phone_help": "Die Angabe einer Telefonnummer ist optional.",
"phone_placeholder": "+49 xxx xxxxxx",
"current_password": "Aktuelles Passwort",
"new_password": "Neues Passwort",
"new_password_help": "Das Passwort muss mindestens 8 Zeichen lang sein.",
"confirm_password": "Neues Passwort bestätigen",
"change_password": "Passwort ändern",
"show_tips": "Tipps anzeigen",
"show_tips_help": "Tipps ein-/ausblenden.",
"email_required": "Die E-Mail-Adresse ist erforderlich!",
"email_invalid": "Bitte geben Sie eine gültige E-Mail-Adresse ein!",
"phone_invalid": "Bitte geben Sie eine gültige Telefonnummer ein!",
"contacts_saved": "Kontakte erfolgreich gespeichert!",
"contacts_save_error": "Fehler beim Speichern. Bitte versuchen Sie es später erneut.",
"settings_saved": "Einstellungen erfolgreich gespeichert! Bitte melden Sie sich erneut an, um die Änderungen zu übernehmen.",
"settings_save_error": "Fehler beim Speichern. Bitte versuchen Sie es später erneut.",
"password_fields_required": "Bitte füllen Sie alle Felder aus!",
"passwords_not_match": "Die neuen Passwörter stimmen nicht überein!",
"password_too_short": "Das neue Passwort muss mindestens 8 Zeichen lang sein!",
"password_changed": "Passwort erfolgreich geändert!",
"password_change_error": "Fehler beim Ändern des Passworts. Bitte versuchen Sie es später erneut."
},
"login": {
"title": "Anmelden",
"username": "Benutzername",
"password": "Passwort",
"login_button": "Anmelden",
"forgot_password": "Passwort vergessen",
"two_factor_title": "Zwei-Faktor-Authentifizierung",
"verification_code": "Bestätigungscode",
"username_placeholder": "Benutzername",
"password_placeholder": "Passwort",
"username_required": "Bitte geben Sie Ihren Benutzernamen ein.",
"password_required": "Bitte geben Sie Ihr Passwort ein.",
"help_login": "Anmeldeprobleme?",
"help_link": "Hilfe",
"system_message": "Systemmeldung",
"privacy_policy": "Datenschutzerklärung",
"kreta_id": "KRÉTA-ID",
"system_notification": "Systembenachrichtigung"
},
"forgot_password": {
"title": "Passwort vergessen",
"om_id": "Ihre OM-ID",
"email": "E-Mail-Adresse",
"om_id_placeholder": "Geben Sie Ihre OM-ID ein",
"email_placeholder": "Geben Sie Ihre E-Mail-Adresse ein",
"om_id_required": "Bitte geben Sie Ihre OM-ID ein.",
"email_required": "Bitte geben Sie Ihre E-Mail-Adresse ein."
},
"two_factor": {
"title": "Zwei-Faktor-Authentifizierung",
"code_placeholder": "Einmalpasswort",
"code_required": "Bitte geben Sie das Einmalpasswort ein.",
"verify_button": "Code überprüfen",
"verifying": "Überprüfung...",
"trust_device": "Diesem Gerät vertrauen"
},
"logout": {
"title": "Abmelden",
"message": "Möchten Sie sich wirklich abmelden?",
"confirm": "Ja",
"cancel": "Abbrechen",
"success": "Erfolgreich abgemeldet!",
"continue": "Weiter"
},
"setup": {
"welcome": "Richten Sie die Erweiterung in wenigen einfachen Schritten ein",
"steps": {
"theme": "Design",
"language": "Sprache",
"finish": "Fertig"
},
"theme": {
"title": "Design wählen",
"description": "Wählen Sie das für Sie am besten geeignete Erscheinungsbild"
},
"language": {
"title": "Sprache wählen",
"description": "Wählen Sie die Sprache, die Sie verwenden möchten"
},
"finish": {
"title": "Alles erledigt!",
"description": "Die Einstellungen wurden erfolgreich gespeichert. Viel Erfolg beim Lernen!",
"about": "Über uns",
"about_desc": "Erfahren Sie mehr über das Projekt",
"support": "Unterstützung",
"support_desc": "Unterstützen Sie die Entwicklung",
"github": "GitHub",
"github_desc": "Sehen Sie sich den Quellcode an",
"discord": "Discord",
"discord_desc": "Treten Sie der Community bei",
"start": "Start"
}
},
"common": {
"save": "Speichern",
"cancel": "Abbrechen",
"close": "Schließen",
"loading": "Laden...",
"error": "Fehler",
"success": "Erfolgreich",
"warning": "Warnung",
"info": "Information",
"yes": "Ja",
"no": "Nein",
"continue": "Weiter",
"back": "Zurück",
"next": "Weiter",
"previous": "Zurück",
"all": "Alle",
"none": "Keine",
"filter": "Filter",
"search": "Suchen",
"select": "Wählen",
"required": "Erforderlich",
"optional": "Optional",
"api_error": "API-Fehler",
"api_load_error": "API-Ladefehler",
"monday": "Montag",
"tuesday": "Dienstag",
"wednesday": "Mittwoch",
"thursday": "Donnerstag",
"friday": "Freitag",
"saturday": "Samstag",
"sunday": "Sonntag",
"mon": "M",
"tue": "D",
"wed": "M",
"thu": "D",
"fri": "F",
"sat": "S",
"sun": "S",
"today": "Heute",
"tomorrow": "Morgen",
"january": "Januar",
"february": "Februar",
"march": "März",
"april": "April",
"may": "Mai",
"june": "Juni",
"july": "Juli",
"august": "August",
"september": "September",
"october": "Oktober",
"november": "November",
"december": "Dezember"
},
"months": {
"january": "Januar",
"february": "Februar",
"march": "März",
"april": "April",
"may": "Mai",
"june": "Juni",
"july": "Juli",
"august": "August",
"september": "September",
"october": "Oktober",
"november": "November",
"december": "Dezember"
},
"roleselect": {
"student_book": "Notenbuch",
"student_description": "Noten, Fehlzeiten, Stundenplan und andere Informationen anzeigen.",
"dkt_title": "Digitaler Kollaborationsraum (DKT)",
"dkt_description": "Klassenzimmerkommunikation und Aufgaben.",
"logout_title": "Abmelden",
"logout_description": "Vom System abmelden",
"role_change_error": "Fehler beim Wechseln der Rolle."
},
"maintenance": {
"title": "Wartung",
"message1": "Das KRÉTA-System wird derzeit aktualisiert und wird bald wieder verfügbar sein.",
"message2": "Vielen Dank für Ihre Geduld und Ihr Verständnis!",
"team": "KRÉTA-Team"
},
"about": {
"title": "Über",
"description": "Firka ist ein Open-Source-Projekt, das eine benutzerdefinierte Benutzeroberfläche für das KRÉTA-System erstellt.",
"support_title": "Unterstützung",
"support_description": "Wenn Ihnen unsere Arbeit gefällt und Sie die Entwicklung unterstützen möchten, können Sie dies folgendermaßen tun:",
"version": "v1.3.0"
},
"app": {
"title": "Firka - KRÉTA",
"settings_title": "Firxa - Einstellungen"
},
"forgotpassword": {
"title": "Passwort vergessen",
"om_id_label": "OM-ID",
"om_id_placeholder": "Geben Sie Ihre OM-ID ein",
"om_id_required": "Die OM-ID ist erforderlich",
"email_label": "E-Mail-Adresse",
"email_placeholder": "Geben Sie Ihre E-Mail-Adresse ein",
"email_required": "Die E-Mail-Adresse ist erforderlich",
"back_to_login": "Zurück zur Anmeldung",
"reset_button": "Passwort zurücksetzen",
"error_message": "Fehler beim Zurücksetzen des Passworts",
"success_message": "Link zum Zurücksetzen des Passworts an Ihre E-Mail-Adresse gesendet",
"invalid_data": "Ungültige Daten",
"invalid_email": "Ungültiges E-Mail-Format",
"recaptcha_required": "Bitte füllen Sie das reCAPTCHA aus"
},
"modal": {
"add_item_title": "Neues Element hinzufügen",
"type_label": "Typ:",
"homework_option": "Hausaufgaben",
"test_option": "Prüfung",
"description_label": "Beschreibung:",
"cancel": "Abbrechen",
"save": "Speichern"
},
"search": {
"choose_school": "Schule wählen",
"privacy_policy": "Datenschutzrichtlinie",
"title": "Schule wählen",
"select_institution": "Bitte wählen Sie eine Einrichtung aus, um fortzufahren!"
},
"icons": {
"cancel": "cancel",
"pending": "pending"
},
"messages": {
"title": "Nachrichten",
"back": "Zurück",
"surveys": "Umfragen",
"loading": "Nachrichten werden geladen...",
"error": {
"title": "Fehler aufgetreten",
"description": "Nachrichten konnten nicht geladen werden.",
"retry": "Erneut versuchen"
},
"empty": {
"title": "Keine Nachrichten",
"description": "Derzeit sind keine eingegangenen Nachrichten vorhanden."
},
"sender": "Absender",
"subject": "Betreff",
"date": "Datum",
"unread": "Ungelesen",
"read": "Gelesen",
"message_detail": {
"title": "Nachrichtendetails",
"loading": "Nachricht wird geladen...",
"error": "Fehler beim Laden der Nachricht.",
"from": "Von",
"to": "An",
"subject": "Betreff",
"date": "Datum",
"content": "Inhalt",
"attachments": "Anhänge",
"no_attachments": "Keine Anhänge",
"reply": "Antworten",
"forward": "Weiterleiten",
"delete": "Löschen",
"mark_read": "Als gelesen markieren",
"mark_unread": "Als ungelesen markieren",
"back_to_messages": "Zurück zu den Nachrichten"
}
}
}

View File

@@ -9,7 +9,7 @@
"theme": "Theme",
"language": "Language",
"tabs": {
"home": "Home",
"appearance": "Appearance",
"settings": "Settings",
"about": "About"
},
@@ -73,17 +73,20 @@
"card_color": "Card color",
"text_color": "Text color",
"preview": "Preview",
"share": "Share theme",
"share": "Share",
"share_description": "Copy the code and share it with others:",
"import": "Import theme",
"import": "Import",
"import_description": "Paste the theme code:",
"import_error_empty": "Please paste the theme code!",
"import_error_invalid": "Invalid theme code!",
"delete_confirm": "Are you sure you want to delete this theme?"
"delete_confirm": "Are you sure you want to delete this theme?",
"delete": "Delete",
"manage": "Manage"
},
"languages": {
"hu": "Magyar",
"en": "English"
"en": "English",
"de": "Deutsch"
},
"about": {
"title": "About",
@@ -94,6 +97,13 @@
"title": "Support",
"description": "If you like our work and would like to support the development, you can do so in the following way:",
"kofi": "Ko-Fi"
},
"error_reporting": {
"title": "Error Reporting",
"enable": "Enable error reporting",
"enable_desc": "Send automatic error reports to developers to help fix the extension",
"report_issue": "Report bug or idea",
"report_issue_desc": "Open a GitHub issue"
}
},
"navigation": {
@@ -256,7 +266,10 @@
"last_30_days": "Last 30 days",
"total_absences": "Total absences",
"topic": "Topic",
"status": "Status"
"status": "Status",
"absence_type": "Absence",
"late_type": "Late",
"minutes": "minutes"
},
"profile": {
"title": "Profile",
@@ -408,8 +421,27 @@
"friday": "Friday",
"saturday": "Saturday",
"sunday": "Sunday",
"mon": "M",
"tue": "T",
"wed": "W",
"thu": "T",
"fri": "F",
"sat": "S",
"sun": "S",
"today": "Today",
"tomorrow": "Tomorrow"
"tomorrow": "Tomorrow",
"january": "January",
"february": "February",
"march": "March",
"april": "April",
"may": "May",
"june": "June",
"july": "July",
"august": "August",
"september": "September",
"october": "October",
"november": "November",
"december": "December"
},
"months": {
"january": "January",

View File

@@ -9,7 +9,7 @@
"theme": "Téma",
"language": "Nyelv",
"tabs": {
"home": "Főoldal",
"appearance": "Megjelenés",
"settings": "Beállítások",
"about": "Névjegy"
},
@@ -63,7 +63,7 @@
"custom_themes": {
"title": "Egyéni témák",
"no_themes": "Még nincsenek egyéni témák",
"create": "Új téma létrehozása",
"create": "Új téma",
"edit": "Téma szerkesztése",
"name": "Téma neve",
"mode": "Mód",
@@ -75,17 +75,20 @@
"card_color": "Kártya szín",
"text_color": "Szöveg szín",
"preview": "Előnézet",
"share": "Téma megosztása",
"share": "Megosztás",
"share_description": "Másold ki a kódot és oszd meg másokkal:",
"import": "Téma importálása",
"import": "Importálás",
"import_description": "Illeszd be a téma kódot:",
"import_error_empty": "Kérlek illeszd be a téma kódot!",
"import_error_invalid": "Érvénytelen téma kód!",
"delete_confirm": "Biztosan törölni szeretnéd ezt a témát?"
"delete_confirm": "Biztosan törölni szeretnéd ezt a témát?",
"delete": "Törlés",
"manage": "Kezelés"
},
"languages": {
"hu": "Magyar",
"en": "English"
"en": "English",
"de": "Deutsch"
},
"about": {
"title": "Névjegy",
@@ -96,6 +99,13 @@
"title": "Támogatás",
"description": "Ha tetszik a munkánk és szeretnéd támogatni a fejlesztést, az alábbi módon teheted meg:",
"kofi": "Ko-Fi"
},
"error_reporting": {
"title": "Hibajelentés",
"enable": "Hibajelentés engedélyezése",
"enable_desc": "Automatikus hibajelentés küldése a fejlesztőknek a bővítmény javításához",
"report_issue": "Hiba vagy ötlet jelentése",
"report_issue_desc": "Nyiss egy GitHub issue-t"
}
},
"navigation": {
@@ -256,9 +266,12 @@
"last_month": "Előző hónap",
"current_semester": "Aktuális félév",
"last_30_days": "Utolsó 30 nap",
"total_absences": "Összes hiányzás",
"total_absences": "Összes mulasztás",
"topic": "Téma",
"status": "Állapot"
"status": "Állapot",
"absence_type": "Hiányzás",
"late_type": "Késés",
"minutes": "perc"
},
"profile": {
"title": "Profil",
@@ -410,8 +423,27 @@
"friday": "péntek",
"saturday": "szombat",
"sunday": "vasárnap",
"mon": "H",
"tue": "K",
"wed": "Sze",
"thu": "Cs",
"fri": "P",
"sat": "Szo",
"sun": "V",
"today": "Ma",
"tomorrow": "Holnap"
"tomorrow": "Holnap",
"january": "Január",
"february": "Február",
"march": "Március",
"april": "Április",
"may": "Május",
"june": "Június",
"july": "Július",
"august": "Augusztus",
"september": "Szeptember",
"october": "Október",
"november": "November",
"december": "December"
},
"months": {
"january": "január",

1
icons/contact.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 3.223a9.003 9.003 0 0 0-5.605 13.592L3 21l4.185-1.395A9.003 9.003 0 0 0 20.777 14m0-4A9.01 9.01 0 0 0 14 3.223M17 12a5 5 0 0 0-5-5m1 5a1 1 0 0 0-1-1"/></svg>

After

Width:  |  Height:  |  Size: 353 B

1
icons/project.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M3 20h.01M7 20a4 4 0 0 0-4-4m8 4a8 8 0 0 0-8-8"/><path fill="currentColor" d="M19 4H5a2 2 0 0 0-2 2v2.5c4 .167 12 2.7 12 11.5h4a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2"/></g></svg>

After

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 213 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
images/firka_logo_128_c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

BIN
images/firka_logo_c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -120,13 +120,11 @@ async function transformLoginPage() {
</div>
`;
document.body.innerHTML = '';
const parser = new DOMParser();
const doc = parser.parseFromString(newHTML, 'text/html');
const tempDiv = doc.body;
while (tempDiv.firstChild) {
document.body.appendChild(tempDiv.firstChild);
}
const template = document.createElement('template');
template.innerHTML = newHTML;
helper.clearElement(document.body);
document.body.appendChild(template.content);
setupEventListeners();
} catch (error) {

View File

@@ -83,13 +83,12 @@ async function transformTwoFactorPage() {
</div>
`;
document.body.innerHTML = '';
const parser = new DOMParser();
const doc = parser.parseFromString(newHTML, 'text/html');
const tempDiv = doc.body;
while (tempDiv.firstChild) {
document.body.appendChild(tempDiv.firstChild);
}
const template = document.createElement('template');
template.innerHTML = newHTML;
helper.clearElement(document.body);
document.body.appendChild(template.content);
applyTheme();
setupEventListeners();
if (typeof loadingScreen !== "undefined") {
@@ -170,7 +169,7 @@ function handleSubmit(event) {
const submitButton = form.querySelector(".btn-kreta");
if (submitButton) {
submitButton.disabled = true;
submitButton.innerHTML = '';
helper.clearElement(submitButton);
const spinnerSpan = document.createElement('span');
spinnerSpan.className = 'spinner';
const textSpan = document.createElement('span');

View File

@@ -34,10 +34,10 @@
</footer>
</div>
`;
document.body.innerHTML = '';
const parser = new DOMParser();
const doc = parser.parseFromString(newHTML, 'text/html');
const tempDiv = doc.body;
helper.clearElement(document.body);
const template = document.createElement('template');
template.innerHTML = newHTML;
const tempDiv = template.content;
while (tempDiv.firstChild) {
document.body.appendChild(tempDiv.firstChild);
}

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Firxa",
"version": "1.4.4",
"version": "1.4.6",
"description": "KRÉTA webes verziójának újraírása",
"icons": {
"128": "images/firka_logo_128.png"
@@ -191,18 +191,6 @@
],
"run_at": "document_end"
},
{
"matches": [
"https://*.e-kreta.hu/Adminisztracio/ElfelejtettJelszo*"
],
"js": [
"forgotpassword/forgotpassword.js"
],
"css": [
"forgotpassword/forgotpassword.css"
],
"run_at": "document_end"
},
{
"matches": [
"https://intezmenykereso.e-kreta.hu/"

View File

@@ -1,7 +1,7 @@
{
"manifest_version": 3,
"name": "Firxa",
"version": "1.4.4",
"version": "1.4.6",
"description": "KRÉTA webes verziójának újraírása",
"icons": {
"128": "images/firka_logo_128.png"
@@ -17,16 +17,14 @@
"tabs"
],
"background": {
"service_worker": "tools/background.js",
"scripts": ["tools/background.js"],
"persistent": false
"scripts": ["tools/background.js"]
},
"browser_specific_settings": {
"gecko": {
"id": "firxa@zan1456.hu",
"strict_min_version": "109.0",
"data_collection_permissions": {
"required": ["none"]
"required": []
}
}
},
@@ -34,6 +32,7 @@
{
"resources": [
"settings/*",
"setup/*",
"global/language.js",
"images/*",
"fonts/*.woff2",
@@ -192,18 +191,6 @@
],
"run_at": "document_end"
},
{
"matches": [
"https://*.e-kreta.hu/Adminisztracio/ElfelejtettJelszo*"
],
"js": [
"forgotpassword/forgotpassword.js"
],
"css": [
"forgotpassword/forgotpassword.css"
],
"run_at": "document_end"
},
{
"matches": [
"https://intezmenykereso.e-kreta.hu/"

View File

@@ -440,7 +440,6 @@ body.modal-open {
align-items: center;
padding: 20px;
border-bottom: 1px solid var(--background-0);
background: var(--background);
border-radius: 12px 12px 0 0;
}
@@ -525,7 +524,7 @@ body.modal-open {
}
.message-info {
background-color: var(--card-card);
background-color: var(--button-secondaryFill);
padding: 15px;
border-radius: 6px;
margin-bottom: 20px;
@@ -564,7 +563,7 @@ body.modal-open {
}
.message-text {
background-color: var(--card-card);
background-color: var(--button-secondaryFill);
border-radius: 6px;
padding: 15px;
line-height: 1.6;
@@ -591,14 +590,14 @@ body.modal-open {
}
.message-attachments {
background: #f9f9f9;
background: var(--button-secondaryFill);
padding: 15px;
border-radius: 6px;
}
.message-attachments h4 {
margin: 0 0 10px 0;
color: #333;
color: var(--text-secondary);
font-size: 1.1em;
}

View File

@@ -42,7 +42,7 @@
function sanitizeHTML(html) {
const div = document.createElement('div');
div.textContent = html;
return div.innerHTML;
return div.textContent;
}
class APIManager {
@@ -159,18 +159,32 @@
const modalContent = document.createElement('div');
modalContent.className = 'modal-content';
modalContent.innerHTML = `
<div class="modal-header">
<h2>Üzenet részletei</h2>
<button class="modal-close">×</button>
</div>
<div class="modal-body">
<div class="loading-content">
<div class="loading-spinner"></div>
<p>Üzenet betöltése...</p>
</div>
</div>
`;
const modalHeader = document.createElement('div');
modalHeader.className = 'modal-header';
const modalTitle = document.createElement('h2');
modalTitle.textContent = 'Üzenet részletei';
const modalClose = document.createElement('button');
modalClose.className = 'modal-close';
modalClose.textContent = '×';
modalHeader.appendChild(modalTitle);
modalHeader.appendChild(modalClose);
const modalBody = document.createElement('div');
modalBody.className = 'modal-body';
const loadingContent = document.createElement('div');
loadingContent.className = 'loading-content';
const loadingSpinner = document.createElement('div');
loadingSpinner.className = 'loading-spinner';
const loadingText = document.createElement('p');
loadingText.textContent = 'Üzenet betöltése...';
loadingContent.appendChild(loadingSpinner);
loadingContent.appendChild(loadingText);
modalBody.appendChild(loadingContent);
modalContent.appendChild(modalHeader);
modalContent.appendChild(modalBody);
modalOverlay.appendChild(modalContent);
document.body.appendChild(modalOverlay);
@@ -213,13 +227,24 @@
console.error('Error loading message details:', error);
const modalContent = document.querySelector('.modal-content');
if (modalContent) {
modalContent.querySelector('.modal-body').innerHTML = `
<div class="error-content">
<h3>Hiba történt</h3>
<p>Az üzenet betöltése sikertelen.</p>
<button class="retry-btn" onclick="openMessageModal(${messageId})">Újrapróbálás</button>
</div>
`;
const modalBody = modalContent.querySelector('.modal-body');
helper.clearElement(modalBody);
const errorContent = document.createElement('div');
errorContent.className = 'error-content';
const errorTitle = document.createElement('h3');
errorTitle.textContent = 'Hiba történt';
const errorText = document.createElement('p');
errorText.textContent = 'Az üzenet betöltése sikertelen.';
const retryBtn = document.createElement('button');
retryBtn.className = 'retry-btn';
retryBtn.textContent = 'Újrapróbálás';
retryBtn.onclick = () => openMessageModal(messageId);
errorContent.appendChild(errorTitle);
errorContent.appendChild(errorText);
errorContent.appendChild(retryBtn);
modalBody.appendChild(errorContent);
}
}
}
@@ -231,38 +256,90 @@
const subject = message.targy || 'Nincs tárgy';
const content = message.szoveg || 'Nincs tartalom';
modalContent.querySelector('.modal-body').innerHTML = `
<div class="message-details">
<div class="message-info">
<div class="info-row">
<span class="info-label">Feladó:</span>
<span class="info-value">${sanitizeHTML(sender)}</span>
</div>
<div class="info-row">
<span class="info-label">Dátum:</span>
<span class="info-value">${date}</span>
</div>
<div class="info-row">
<span class="info-label">Tárgy:</span>
<span class="info-value">${sanitizeHTML(subject)}</span>
</div>
</div>
<div class="message-content">
<h4>Üzenet tartalma:</h4>
<div class="message-text">${content}</div>
</div>
${message.csatolmanyok && message.csatolmanyok.length > 0 ? `
<div class="message-attachments">
<h4>Mellékletek:</h4>
<ul>
${message.csatolmanyok.map(attachment => `
<li><a href="#" onclick="downloadAttachment('${attachment.azonosito}')">${sanitizeHTML(attachment.nev)}</a></li>
`).join('')}
</ul>
</div>
` : ''}
</div>
`;
const modalBody = modalContent.querySelector('.modal-body');
helper.clearElement(modalBody);
const messageDetails = document.createElement('div');
messageDetails.className = 'message-details';
const messageInfo = document.createElement('div');
messageInfo.className = 'message-info';
const senderRow = document.createElement('div');
senderRow.className = 'info-row';
const senderLabel = document.createElement('span');
senderLabel.className = 'info-label';
senderLabel.textContent = 'Feladó:';
const senderValue = document.createElement('span');
senderValue.className = 'info-value';
senderValue.textContent = sanitizeHTML(sender);
senderRow.appendChild(senderLabel);
senderRow.appendChild(senderValue);
messageInfo.appendChild(senderRow);
const dateRow = document.createElement('div');
dateRow.className = 'info-row';
const dateLabel = document.createElement('span');
dateLabel.className = 'info-label';
dateLabel.textContent = 'Dátum:';
const dateValue = document.createElement('span');
dateValue.className = 'info-value';
dateValue.textContent = date;
dateRow.appendChild(dateLabel);
dateRow.appendChild(dateValue);
messageInfo.appendChild(dateRow);
const subjectRow = document.createElement('div');
subjectRow.className = 'info-row';
const subjectLabel = document.createElement('span');
subjectLabel.className = 'info-label';
subjectLabel.textContent = 'Tárgy:';
const subjectValue = document.createElement('span');
subjectValue.className = 'info-value';
subjectValue.textContent = sanitizeHTML(subject);
subjectRow.appendChild(subjectLabel);
subjectRow.appendChild(subjectValue);
messageInfo.appendChild(subjectRow);
messageDetails.appendChild(messageInfo);
const messageContent = document.createElement('div');
messageContent.className = 'message-content';
const contentTitle = document.createElement('h4');
contentTitle.textContent = 'Üzenet tartalma:';
messageContent.appendChild(contentTitle);
const messageText = document.createElement('div');
messageText.className = 'message-text';
messageText.innerHTML = content;
messageContent.appendChild(messageText);
messageDetails.appendChild(messageContent);
if (message.csatolmanyok && message.csatolmanyok.length > 0) {
const messageAttachments = document.createElement('div');
messageAttachments.className = 'message-attachments';
const attachTitle = document.createElement('h4');
attachTitle.textContent = 'Mellékletek:';
messageAttachments.appendChild(attachTitle);
const attachList = document.createElement('ul');
message.csatolmanyok.forEach(attachment => {
const li = document.createElement('li');
const a = document.createElement('a');
a.href = '#';
a.textContent = sanitizeHTML(attachment.fajlNev || attachment.nev || 'Ismeretlen fájl');
a.onclick = (e) => {
e.preventDefault();
downloadAttachment(attachment.azonosito, attachment.fajlNev || attachment.nev);
};
li.appendChild(a);
attachList.appendChild(li);
});
messageAttachments.appendChild(attachList);
messageDetails.appendChild(messageAttachments);
}
modalBody.appendChild(messageDetails);
}
function closeMessageModal() {
@@ -272,47 +349,90 @@
}
document.body.classList.remove('modal-open');
}
async function downloadAttachment(azonosito, fileName) {
try {
const response = await chrome.runtime.sendMessage({
action: 'download_attachment',
azonosito: azonosito,
fileName: fileName
});
if (response.success && response.data) {
const a = document.createElement('a');
a.href = response.data;
a.download = response.fileName || 'letoltes';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
} else {
console.error('Melléklet letöltési hiba:', response.error);
alert('Nem sikerült letölteni a mellékletet.');
}
} catch (error) {
console.error('Melléklet letöltési hiba:', error);
alert('Hiba történt a melléklet letöltése során.');
}
}
window.openMessageModal = openMessageModal;
window.closeMessageModal = closeMessageModal;
function createLoadingState() {
const loadingDiv = document.createElement('div');
loadingDiv.className = 'loading-state';
loadingDiv.innerHTML = `
<div class="loading-content">
<div class="loading-spinner"></div>
<p>${LanguageManager.t('messages.loading', 'Üzenetek betöltése...')}</p>
</div>
`;
const loadingContent = document.createElement('div');
loadingContent.className = 'loading-content';
const spinner = document.createElement('div');
spinner.className = 'loading-spinner';
const text = document.createElement('p');
text.textContent = LanguageManager.t('messages.loading', 'Üzenetek betöltése...');
loadingContent.appendChild(spinner);
loadingContent.appendChild(text);
loadingDiv.appendChild(loadingContent);
return loadingDiv;
}
function createErrorState(onRetry) {
const errorDiv = document.createElement('div');
errorDiv.className = 'error-state';
errorDiv.innerHTML = `
<div class="error-content">
<h3>${LanguageManager.t('messages.error.title', 'Hiba történt')}</h3>
<p>${LanguageManager.t('messages.error.description', 'Az üzenetek betöltése sikertelen volt.')}</p>
<button class="retry-btn">${LanguageManager.t('messages.error.retry', 'Újrapróbálás')}</button>
</div>
`;
const retryBtn = errorDiv.querySelector('.retry-btn');
const errorContent = document.createElement('div');
errorContent.className = 'error-content';
const title = document.createElement('h3');
title.textContent = LanguageManager.t('messages.error.title', 'Hiba történt');
const desc = document.createElement('p');
desc.textContent = LanguageManager.t('messages.error.description', 'Az üzenetek betöltése sikertelen volt.');
const retryBtn = document.createElement('button');
retryBtn.className = 'retry-btn';
retryBtn.textContent = LanguageManager.t('messages.error.retry', 'Újrapróbálás');
retryBtn.addEventListener('click', onRetry);
errorContent.appendChild(title);
errorContent.appendChild(desc);
errorContent.appendChild(retryBtn);
errorDiv.appendChild(errorContent);
return errorDiv;
}
function createEmptyState() {
const emptyDiv = document.createElement('div');
emptyDiv.className = 'empty-state';
emptyDiv.innerHTML = `
<div class="empty-content">
<h3>${LanguageManager.t('messages.empty.title', 'Nincsenek üzenetek')}</h3>
<p>${LanguageManager.t('messages.empty.description', 'Jelenleg nincsenek elérhető üzenetek.')}</p>
</div>
`;
const emptyContent = document.createElement('div');
emptyContent.className = 'empty-content';
const title = document.createElement('h3');
title.textContent = LanguageManager.t('messages.empty.title', 'Nincsenek üzenetek');
const desc = document.createElement('p');
desc.textContent = LanguageManager.t('messages.empty.description', 'Jelenleg nincsenek elérhető üzenetek.');
emptyContent.appendChild(title);
emptyContent.appendChild(desc);
emptyDiv.appendChild(emptyContent);
return emptyDiv;
}
@@ -332,18 +452,42 @@
const subject = message.uzenetTargy || 'Nincs tárgy';
const date = formatDate(message.uzenetKuldesDatum);
const hasAttachment = message.hasCsatolmany;
const cardHeader = document.createElement('div');
cardHeader.className = 'message-card-header';
card.innerHTML = `
<div class="message-card-header">
<div class="sender-info">
<span class="sender-name">${sanitizeHTML(senderName)}</span>
${!message.isElolvasva ? '<span class="unread-indicator"></span>' : ''}
</div>
<div class="message-date">${date}</div>
</div>
<div class="message-subject">${sanitizeHTML(subject)}</div>
${hasAttachment ? '<div class="attachment-indicator">📎</div>' : ''}
`;
const senderInfo = document.createElement('div');
senderInfo.className = 'sender-info';
const senderNameSpan = document.createElement('span');
senderNameSpan.className = 'sender-name';
senderNameSpan.textContent = sanitizeHTML(senderName);
senderInfo.appendChild(senderNameSpan);
if (!message.isElolvasva) {
const unreadIndicator = document.createElement('span');
unreadIndicator.className = 'unread-indicator';
senderInfo.appendChild(unreadIndicator);
}
const messageDate = document.createElement('div');
messageDate.className = 'message-date';
messageDate.textContent = date;
cardHeader.appendChild(senderInfo);
cardHeader.appendChild(messageDate);
card.appendChild(cardHeader);
const messageSubject = document.createElement('div');
messageSubject.className = 'message-subject';
messageSubject.textContent = sanitizeHTML(subject);
card.appendChild(messageSubject);
if (hasAttachment) {
const attachmentIndicator = document.createElement('div');
attachmentIndicator.className = 'attachment-indicator';
attachmentIndicator.textContent = '📎';
card.appendChild(attachmentIndicator);
}
return card;
@@ -397,41 +541,111 @@
function renderBulkActions(container) {
const bulk = document.createElement('div');
bulk.className = 'bulk-actions-card';
bulk.innerHTML = `
<div class="bulk-actions-left">
<div class="view-toggle">
<button id="viewInboxBtn" class="${currentView==='inbox'?'active':''}" title="Beérkezett">
<img src="${chrome.runtime.getURL('icons/messages-active.svg')}" alt="Beérkezett">
</button>
<button id="viewTrashBtn" class="${currentView==='trash'?'active':''}" title="Törölt">
<img src="${chrome.runtime.getURL('icons/delete.svg')}" alt="Törölt">
</button>
</div>
<button id="toggleSelectionModeBtn" class="bulk-btn" title="Kijelölés mód">
<img src="${chrome.runtime.getURL('icons/select.svg')}" alt="Kijelölés mód">
</button>
<button id="selectAllBtn" class="bulk-btn" title="Mind kijelöl">
<img src="${chrome.runtime.getURL('icons/select-all.svg')}" alt="Mind kijelöl">
</button>
<button id="clearSelectionBtn" class="bulk-btn" title="Kijelölés törlése">
<img src="${chrome.runtime.getURL('icons/select-none.svg')}" alt="Kijelölés törlése">
</button>
</div>
<div class="bulk-actions-right">
<button id="markReadBtn" class="bulk-btn" title="Olvasott">
<img src="${chrome.runtime.getURL('icons/eye-on.svg')}" alt="Olvasott">
</button>
<button id="markUnreadBtn" class="bulk-btn" title="Olvasatlan">
<img src="${chrome.runtime.getURL('icons/eye-off.svg')}" alt="Olvasatlan">
</button>
<button id="deleteBtn" class="bulk-btn" title="Törlés">
<img src="${chrome.runtime.getURL('icons/trash.svg')}" alt="Törlés">
</button>
<button id="restoreBtn" class="bulk-btn" title="Visszaállítás">
<img src="${chrome.runtime.getURL('icons/undo.svg')}" alt="Visszaállítás">
</button>
</div>
`;
const bulkActionsLeft = document.createElement('div');
bulkActionsLeft.className = 'bulk-actions-left';
const viewToggle = document.createElement('div');
viewToggle.className = 'view-toggle';
const viewInboxBtn = document.createElement('button');
viewInboxBtn.id = 'viewInboxBtn';
viewInboxBtn.className = currentView === 'inbox' ? 'active' : '';
viewInboxBtn.title = 'Beérkezett';
const inboxImg = document.createElement('img');
inboxImg.src = chrome.runtime.getURL('icons/messages-active.svg');
inboxImg.alt = 'Beérkezett';
viewInboxBtn.appendChild(inboxImg);
const viewTrashBtn = document.createElement('button');
viewTrashBtn.id = 'viewTrashBtn';
viewTrashBtn.className = currentView === 'trash' ? 'active' : '';
viewTrashBtn.title = 'Törölt';
const trashImg = document.createElement('img');
trashImg.src = chrome.runtime.getURL('icons/delete.svg');
trashImg.alt = 'Törölt';
viewTrashBtn.appendChild(trashImg);
viewToggle.appendChild(viewInboxBtn);
viewToggle.appendChild(viewTrashBtn);
bulkActionsLeft.appendChild(viewToggle);
const toggleSelBtn = document.createElement('button');
toggleSelBtn.id = 'toggleSelectionModeBtn';
toggleSelBtn.className = 'bulk-btn';
toggleSelBtn.title = 'Kijelölés mód';
const selectImg = document.createElement('img');
selectImg.src = chrome.runtime.getURL('icons/select.svg');
selectImg.alt = 'Kijelölés mód';
toggleSelBtn.appendChild(selectImg);
bulkActionsLeft.appendChild(toggleSelBtn);
const selectAllBtn = document.createElement('button');
selectAllBtn.id = 'selectAllBtn';
selectAllBtn.className = 'bulk-btn';
selectAllBtn.title = 'Mind kijelöl';
const selectAllImg = document.createElement('img');
selectAllImg.src = chrome.runtime.getURL('icons/select-all.svg');
selectAllImg.alt = 'Mind kijelöl';
selectAllBtn.appendChild(selectAllImg);
bulkActionsLeft.appendChild(selectAllBtn);
const clearSelBtn = document.createElement('button');
clearSelBtn.id = 'clearSelectionBtn';
clearSelBtn.className = 'bulk-btn';
clearSelBtn.title = 'Kijelölés törlése';
const clearSelImg = document.createElement('img');
clearSelImg.src = chrome.runtime.getURL('icons/select-none.svg');
clearSelImg.alt = 'Kijelölés törlése';
clearSelBtn.appendChild(clearSelImg);
bulkActionsLeft.appendChild(clearSelBtn);
bulk.appendChild(bulkActionsLeft);
const bulkActionsRight = document.createElement('div');
bulkActionsRight.className = 'bulk-actions-right';
const markReadBtn = document.createElement('button');
markReadBtn.id = 'markReadBtn';
markReadBtn.className = 'bulk-btn';
markReadBtn.title = 'Olvasott';
const markReadImg = document.createElement('img');
markReadImg.src = chrome.runtime.getURL('icons/eye-on.svg');
markReadImg.alt = 'Olvasott';
markReadBtn.appendChild(markReadImg);
bulkActionsRight.appendChild(markReadBtn);
const markUnreadBtn = document.createElement('button');
markUnreadBtn.id = 'markUnreadBtn';
markUnreadBtn.className = 'bulk-btn';
markUnreadBtn.title = 'Olvasatlan';
const markUnreadImg = document.createElement('img');
markUnreadImg.src = chrome.runtime.getURL('icons/eye-off.svg');
markUnreadImg.alt = 'Olvasatlan';
markUnreadBtn.appendChild(markUnreadImg);
bulkActionsRight.appendChild(markUnreadBtn);
const deleteBtn = document.createElement('button');
deleteBtn.id = 'deleteBtn';
deleteBtn.className = 'bulk-btn';
deleteBtn.title = 'Törlés';
const deleteImg = document.createElement('img');
deleteImg.src = chrome.runtime.getURL('icons/trash.svg');
deleteImg.alt = 'Törlés';
deleteBtn.appendChild(deleteImg);
bulkActionsRight.appendChild(deleteBtn);
const restoreBtn = document.createElement('button');
restoreBtn.id = 'restoreBtn';
restoreBtn.className = 'bulk-btn';
restoreBtn.title = 'Visszaállítás';
const restoreImg = document.createElement('img');
restoreImg.src = chrome.runtime.getURL('icons/undo.svg');
restoreImg.alt = 'Visszaállítás';
restoreBtn.appendChild(restoreImg);
bulkActionsRight.appendChild(restoreBtn);
bulk.appendChild(bulkActionsRight);
container.appendChild(bulk);
bulk.querySelector('#viewInboxBtn').addEventListener('click', () => switchView('inbox'));
@@ -568,13 +782,13 @@ async function switchView(view) {
async function transformMessagesPage() {
try {
await waitForTranslations();
document.body.innerHTML = '';
helper.clearElement(document.body);
const kretaContainer = document.createElement('div');
kretaContainer.className = 'kreta-container';
const headerDiv = document.createElement('div');
const parser = new DOMParser();
const headerDoc = parser.parseFromString(await createTemplate.header(), 'text/html');
const headerContent = headerDoc.body;
const template = document.createElement('template');
template.innerHTML = await createTemplate.header();
const headerContent = template.content;
while (headerContent.firstChild) {
headerDiv.appendChild(headerContent.firstChild);
}

View File

@@ -30,7 +30,7 @@
const backButton = document.createElement('button');
backButton.id = 'firka-back-button';
backButton.innerHTML = '← Vissza';
backButton.textContent = '← Vissza';
backButton.style.cssText = `
position: static;
margin: 20px;

View File

@@ -154,15 +154,15 @@
if (userName) {
await storageManager.set("userName", userName);
}
document.body.innerHTML = '';
const parser = new DOMParser();
const doc = parser.parseFromString(createHTML(
helper.clearElement(document.body);
const template = document.createElement('template');
template.innerHTML = createHTML(
schoolCode,
fullSchoolName,
userName,
settings,
), 'text/html');
const tempDiv = doc.body;
);
const tempDiv = template.content;
while (tempDiv.firstChild) {
document.body.appendChild(tempDiv.firstChild);
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,9 +23,9 @@
</header>
<div class="tab-navigation">
<button class="tab-button active" data-tab="home">
<span class="material-icons-round">home</span>
<span data-i18n="settings.tabs.home">Főoldal</span>
<button class="tab-button active" data-tab="appearance">
<span class="material-icons-round">palette</span>
<span data-i18n="settings.tabs.appearance">Megjelenés</span>
</button>
<button class="tab-button" data-tab="settings">
<span class="material-icons-round">tune</span>
@@ -37,16 +37,32 @@
</button>
</div>
<div class="tab-content active" id="tab-home">
<div class="tab-content active" id="tab-appearance">
<div class="settings-card">
<h2 data-i18n="settings.appearance">Megjelenés</h2>
<div class="settings-group">
<div class="setting-section">
<div class="setting-header">
<span class="material-icons-round">palette</span>
<span data-i18n="settings.theme">Téma</span>
<div class="setting-header theme-header">
<div class="setting-header-left">
<span class="material-icons-round">palette</span>
<span data-i18n="settings.theme">Téma</span>
</div>
<div class="theme-manage-dropdown">
<button class="theme-manage-btn" id="themeManageBtn">
<span class="material-icons-round">settings</span>
</button>
<div class="theme-manage-menu" id="themeManageMenu">
<button class="theme-manage-item" id="addCustomTheme">
<span class="material-icons-round">add</span>
<span data-i18n="settings.custom_themes.create">Új téma létrehozása</span>
</button>
<button class="theme-manage-item" id="importCustomTheme">
<span class="material-icons-round">download</span>
<span data-i18n="settings.custom_themes.import">Importálás</span>
</button>
</div>
</div>
</div>
<div class="theme-grid">
<div class="theme-grid" id="allThemesGrid">
<button class="theme-option" data-theme="light-green">
<div class="theme-preview light-green">
<div class="preview-header"></div>
@@ -67,22 +83,6 @@
<span class="theme-name" data-i18n="settings.themes.dark_green">Sötét Zöld</span>
</button>
</div>
<div class="custom-themes-section">
<div class="custom-themes-header">
<span data-i18n="settings.custom_themes.title">Egyéni témák</span>
<div class="custom-themes-buttons">
<button class="theme-btn" id="addCustomTheme" title="Új téma">
<span class="material-icons-round">add</span>
</button>
<button class="theme-btn" id="importCustomTheme" title="Téma importálása">
<span class="material-icons-round">download</span>
</button>
</div>
</div>
<div class="theme-grid custom-themes-grid" id="customThemesGrid">
</div>
</div>
</div>
<div class="setting-section">
<div class="setting-header">
@@ -96,6 +96,9 @@
<button class="language-option" data-language="en">
<span class="language-name" data-i18n="settings.languages.en">English</span>
</button>
<button class="language-option" data-language="de">
<span class="language-name" data-i18n="settings.languages.de">Deutsch</span>
</button>
</div>
</div>
</div>
@@ -118,6 +121,30 @@
<p data-i18n="settings.page_settings.no_settings">Ehhez az oldalhoz nincsenek egyéni beállítások.</p>
</div>
</div>
</div>
<div class="error-reporting-card">
<h2 data-i18n="settings.error_reporting.title">Hibajelentés</h2>
<div class="error-reporting-content">
<div class="setting-item">
<div class="setting-item-info">
<div class="setting-item-label" data-i18n="settings.error_reporting.enable">Hibajelentés engedélyezése</div>
<div class="setting-item-description" data-i18n="settings.error_reporting.enable_desc">Automatikus hibajelentés küldése a fejlesztőknek a bővítmény javításához</div>
</div>
<label class="toggle-switch">
<input type="checkbox" id="errorReportingToggle" checked>
<span class="toggle-slider"></span>
</label>
</div>
<div class="report-issue-button-wrapper">
<a href="https://github.com/QwIT-Development/firka-extension/issues/new" target="_blank" class="report-issue-button">
<span class="material-icons-round">bug_report</span>
<div class="report-issue-text">
<span class="report-issue-title" data-i18n="settings.error_reporting.report_issue">Hiba vagy ötlet jelentése</span>
<span class="report-issue-desc" data-i18n="settings.error_reporting.report_issue_desc">Nyiss egy GitHub issue-t</span>
</div>
</a>
</div>
</div>
</div>
</div>
@@ -161,6 +188,39 @@
</div>
<div class="modal-overlay" id="updateModal">
<div class="modal-content update-modal">
<div class="modal-header">
<h3 data-i18n="settings.update.title">Új verzió érhető el!</h3>
</div>
<div class="modal-body">
<div class="update-version-info">
<div class="version-badge current">
<span class="version-label" data-i18n="settings.update.current_version">Jelenlegi verzió:</span>
<span class="version-number" id="currentVersion">-</span>
</div>
<span class="material-icons-round version-arrow">arrow_forward</span>
<div class="version-badge latest">
<span class="version-label" data-i18n="settings.update.latest_version">Legújabb verzió:</span>
<span class="version-number" id="latestVersion">-</span>
</div>
</div>
<div class="update-changelog-section">
<h4 data-i18n="settings.update.whats_new">Újdonságok:</h4>
<div class="update-changelog" id="updateChangelog">
<p data-i18n="settings.update.loading">Betöltés...</p>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="dismissUpdate" data-i18n="settings.update.dismiss">Bezárás</button>
<a class="btn-primary update-button" id="updateButton" href="#" target="_blank">
<span data-i18n="settings.update.download">Letöltés</span>
</a>
</div>
</div>
</div>
<div class="modal-overlay" id="themeEditorModal">
<div class="modal-content theme-editor-modal">
<div class="modal-header">
@@ -277,6 +337,7 @@
</div>
</div>
<script src="../tools/helper.js"></script>
<script src="../tools/storageManager.js"></script>
<script src="../global/language.js"></script>
<script src="index.js"></script>

View File

@@ -11,7 +11,7 @@ document.addEventListener("DOMContentLoaded", async () => {
const tabButtons = document.querySelectorAll(".tab-button");
const tabContents = document.querySelectorAll(".tab-content");
const lastTab = localStorage.getItem("settingsLastTab") || "home";
const lastTab = localStorage.getItem("settingsLastTab") || "appearance";
switchTab(lastTab);
tabButtons.forEach(button => {
@@ -242,19 +242,30 @@ document.addEventListener("DOMContentLoaded", async () => {
const settings = pageSettings[pageType] || [];
if (settings.length === 0) {
container.innerHTML = `
<div class="no-settings-placeholder">
<span class="material-icons-round">settings_suggest</span>
<p data-i18n="settings.page_settings.no_settings">Ehhez az oldalhoz nincsenek egyéni beállítások.</p>
</div>
`;
helper.clearElement(container);
const placeholder = document.createElement('div');
placeholder.className = 'no-settings-placeholder';
const icon = document.createElement('span');
icon.className = 'material-icons-round';
icon.textContent = 'settings_suggest';
const text = document.createElement('p');
text.setAttribute('data-i18n', 'settings.page_settings.no_settings');
text.textContent = 'Ehhez az oldalhoz nincsenek egyéni beállítások.';
placeholder.appendChild(icon);
placeholder.appendChild(text);
container.appendChild(placeholder);
if (window.LanguageManager) {
window.LanguageManager.loadTranslationsForPage();
}
return;
}
container.innerHTML = settings.map(setting => renderSettingItem(setting)).join("");
const template = document.createElement('template');
template.innerHTML = settings.map(setting => renderSettingItem(setting)).join("");
helper.clearElement(container);
container.appendChild(template.content);
initSettingItems(container);
if (window.LanguageManager) {
@@ -358,75 +369,106 @@ document.addEventListener("DOMContentLoaded", async () => {
}
function renderCustomThemes() {
const grid = document.getElementById("customThemesGrid");
const grid = document.getElementById("allThemesGrid");
if (!grid) return;
grid.querySelectorAll(".custom-theme-option").forEach(el => el.remove());
document.querySelectorAll(".custom-theme-dropdown").forEach(el => el.remove());
if (customThemes.length === 0) {
grid.innerHTML = `<div class="no-custom-themes" data-i18n="settings.custom_themes.no_themes">Még nincsenek egyéni témák</div>`;
if (window.LanguageManager) {
window.LanguageManager.loadTranslationsForPage();
}
return;
}
grid.innerHTML = customThemes.map(theme => `
const template = document.createElement('template');
template.innerHTML = customThemes.map(theme => `
<button class="theme-option custom-theme-option" data-theme="custom-${theme.id}">
<div class="theme-preview" style="background: ${theme.colors.background};">
<div class="preview-header" style="background: ${theme.colors.card};"></div>
<div class="preview-content">
<div class="preview-card" style="background: ${theme.colors.accent}20; border: 1px solid ${theme.colors.accent};"></div>
</div>
</div>
<div class="theme-info">
<span class="theme-name">${theme.name}</span>
<div class="theme-actions">
<span class="theme-action-btn edit-theme" data-id="${theme.id}" title="Szerkesztés">
<span class="material-icons-round">edit</span>
</span>
<span class="theme-action-btn share-theme" data-id="${theme.id}" title="Megosztás">
<span class="material-icons-round">share</span>
</span>
<span class="theme-action-btn delete-theme" data-id="${theme.id}" title="Törlés">
<span class="material-icons-round">delete</span>
</span>
<div class="custom-theme-settings-btn" role="button" tabindex="0" data-id="${theme.id}">
<span class="material-icons-round">settings</span>
</div>
</div>
</button>
`).join("");
grid.appendChild(template.content);
const dropdownTemplate = document.createElement('template');
dropdownTemplate.innerHTML = customThemes.map(theme => `
<div class="custom-theme-dropdown" data-dropdown-id="${theme.id}">
<button class="custom-theme-dropdown-item" data-action="share" data-id="${theme.id}">
<span class="material-icons-round">share</span>
<span data-i18n="settings.custom_themes.share">Megosztás</span>
</button>
<button class="custom-theme-dropdown-item" data-action="edit" data-id="${theme.id}">
<span class="material-icons-round">edit</span>
<span data-i18n="settings.custom_themes.edit">Szerkesztés</span>
</button>
<button class="custom-theme-dropdown-item delete" data-action="delete" data-id="${theme.id}">
<span class="material-icons-round">delete</span>
<span data-i18n="settings.custom_themes.delete">Törlés</span>
</button>
</div>
`).join("");
document.body.appendChild(dropdownTemplate.content);
grid.querySelectorAll(".custom-theme-option").forEach(btn => {
btn.addEventListener("click", (e) => {
if (e.target.closest(".theme-action-btn")) return;
if (e.target.closest(".theme-hover-btn")) return;
const themeId = btn.dataset.theme;
applyTheme(themeId);
});
});
grid.querySelectorAll(".edit-theme").forEach(btn => {
grid.querySelectorAll(".custom-theme-settings-btn").forEach(btn => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const id = btn.dataset.id;
editTheme(id);
const dropdown = document.querySelector(`[data-dropdown-id="${id}"]`);
document.querySelectorAll(".custom-theme-dropdown").forEach(d => {
if (d !== dropdown) d.classList.remove("active");
});
if (dropdown) {
const isActive = dropdown.classList.contains("active");
if (!isActive) {
const btnRect = btn.getBoundingClientRect();
dropdown.style.top = `${btnRect.bottom + 4}px`;
dropdown.style.left = `${btnRect.right - 160}px`;
}
dropdown.classList.toggle("active");
}
});
});
grid.querySelectorAll(".share-theme").forEach(btn => {
document.querySelectorAll(".custom-theme-dropdown-item").forEach(btn => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const action = btn.dataset.action;
const id = btn.dataset.id;
shareTheme(id);
const dropdown = btn.closest(".custom-theme-dropdown");
dropdown?.classList.remove("active");
if (action === "share") shareTheme(id);
else if (action === "edit") editTheme(id);
else if (action === "delete") deleteTheme(id);
});
});
grid.querySelectorAll(".delete-theme").forEach(btn => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const id = btn.dataset.id;
deleteTheme(id);
});
document.addEventListener("click", (e) => {
if (!e.target.closest(".custom-theme-settings")) {
document.querySelectorAll(".custom-theme-dropdown").forEach(d => d.classList.remove("active"));
}
});
updateThemeButtons(getCurrentTheme());
if (window.LanguageManager) {
window.LanguageManager.loadTranslationsForPage();
}
}
function openThemeEditor(theme = null) {
@@ -556,8 +598,45 @@ document.addEventListener("DOMContentLoaded", async () => {
};
const code = btoa(JSON.stringify(shareData));
document.getElementById("shareCode").value = code;
document.getElementById("shareThemeModal").classList.add("active");
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(code).then(() => {
showNotification('Téma kód vágólapra másolva!');
}).catch(() => {
copyToClipboardFallback(code);
showNotification('Téma kód vágólapra másolva!');
});
} else {
copyToClipboardFallback(code);
showNotification('Téma kód vágólapra másolva!');
}
}
function copyToClipboardFallback(text) {
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
}
function showNotification(message) {
const notification = document.createElement('div');
notification.className = 'notification';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('show');
}, 10);
setTimeout(() => {
notification.classList.remove('show');
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
function deleteTheme(id) {
@@ -669,19 +748,14 @@ document.addEventListener("DOMContentLoaded", async () => {
}
async function applyLanguage(language) {
localStorage.setItem("languagePreference", language);
updateLanguageButtons(language);
const tabs = await chrome.tabs.query({});
tabs.forEach((tab) => {
chrome.tabs
.sendMessage(tab.id, {
action: "changeLanguage",
language: language,
})
.catch(() => {});
});
try {
if (window.LanguageManager) {
await window.LanguageManager.changeLanguage(language);
}
updateLanguageButtons(language);
} catch (error) {
console.error("Error applying language:", error);
}
}
async function applyTheme(theme) {
@@ -716,7 +790,7 @@ document.addEventListener("DOMContentLoaded", async () => {
function applyCustomThemeColors(theme) {
const root = document.documentElement;
const isDark = theme.mode === "dark";
root.style.setProperty("--background", theme.colors.background);
root.style.setProperty("--card-card", theme.colors.card);
root.style.setProperty("--accent-accent", theme.colors.accent);
@@ -728,6 +802,15 @@ document.addEventListener("DOMContentLoaded", async () => {
root.style.setProperty("--accent-secondary", isDark ? lightenColor(theme.colors.accent, 20) : darkenColor(theme.colors.accent, 20));
root.style.setProperty("--shadow-blur", isDark ? "0" : "2px");
root.style.setProperty("--accent-shadow", isDark ? "#0000" : theme.colors.accent + "26");
root.style.setProperty("--warning-accent", "#FFA046");
root.style.setProperty("--warning-text", isDark ? "#f0b37a" : "#8F531B");
root.style.setProperty("--warning-15", "#ffa04626");
root.style.setProperty("--warning-card", isDark ? "#201203" : "#FAEBDC");
root.style.setProperty("--error-accent", "#FF54A1");
root.style.setProperty("--error-text", isDark ? "#f59ec5" : "#8F1B4F");
root.style.setProperty("--error-15", "#FF54A126");
root.style.setProperty("--error-card", isDark ? "#1e030f" : "#FADCE9");
root.style.setProperty("--icon-filter", hexToFilter(theme.colors.accent));
}
@@ -735,7 +818,7 @@ document.addEventListener("DOMContentLoaded", async () => {
const root = document.documentElement;
const properties = [
"--background",
"--card-card",
"--card-card",
"--accent-accent",
"--text-primary",
"--text-secondary",
@@ -745,9 +828,17 @@ document.addEventListener("DOMContentLoaded", async () => {
"--accent-secondary",
"--shadow-blur",
"--accent-shadow",
"--icon-filter"
"--icon-filter",
"--warning-accent",
"--warning-text",
"--warning-15",
"--warning-card",
"--error-accent",
"--error-text",
"--error-15",
"--error-card"
];
properties.forEach(prop => root.style.removeProperty(prop));
}
@@ -830,12 +921,31 @@ document.addEventListener("DOMContentLoaded", async () => {
});
});
document.getElementById("addCustomTheme")?.addEventListener("click", () => openThemeEditor());
const themeManageBtn = document.getElementById("themeManageBtn");
const themeManageMenu = document.getElementById("themeManageMenu");
themeManageBtn?.addEventListener("click", (e) => {
e.stopPropagation();
themeManageMenu.classList.toggle("active");
});
document.addEventListener("click", (e) => {
if (!e.target.closest(".theme-manage-dropdown")) {
themeManageMenu?.classList.remove("active");
}
});
document.getElementById("addCustomTheme")?.addEventListener("click", () => {
themeManageMenu?.classList.remove("active");
openThemeEditor();
});
document.getElementById("closeThemeEditor")?.addEventListener("click", closeThemeEditor);
document.getElementById("cancelThemeEditor")?.addEventListener("click", closeThemeEditor);
document.getElementById("saveTheme")?.addEventListener("click", saveThemeFromEditor);
document.getElementById("importCustomTheme")?.addEventListener("click", () => {
themeManageMenu?.classList.remove("active");
document.getElementById("importThemeModal").classList.add("active");
});
@@ -937,4 +1047,122 @@ document.addEventListener("DOMContentLoaded", async () => {
});
await initTabs();
await initErrorReporting();
await checkForUpdates();
});
async function initErrorReporting() {
const toggle = document.getElementById('errorReportingToggle');
if (!toggle) return;
const result = await storageManager.get('firka_errorReporting', true);
toggle.checked = result !== false;
toggle.addEventListener('change', async () => {
const enabled = toggle.checked;
await storageManager.set('firka_errorReporting', enabled);
const tabs = await chrome.tabs.query({});
tabs.forEach((tab) => {
chrome.tabs.sendMessage(tab.id, {
action: 'errorReportingChanged',
enabled: enabled
}).catch(() => {});
});
});
}
async function checkForUpdates() {
try {
const manifest = chrome.runtime.getManifest();
const currentVersion = manifest.version;
const response = await fetch('https://api.github.com/repos/QwIT-Development/firka-extension/releases/latest');
if (!response.ok) {
console.error('Failed to fetch latest release');
return;
}
const latestRelease = await response.json();
const latestVersion = latestRelease.tag_name.replace(/^v/, '');
if (compareVersions(latestVersion, currentVersion) > 0) {
showUpdateModal(currentVersion, latestVersion, latestRelease);
}
} catch (error) {
console.error('Error checking for updates:', error);
}
}
function compareVersions(v1, v2) {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const part1 = parts1[i] || 0;
const part2 = parts2[i] || 0;
if (part1 > part2) return 1;
if (part1 < part2) return -1;
}
return 0;
}
function parseMarkdown(markdown) {
let html = markdown;
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
html = html.replace(/_(.+?)_/g, '<em>$1</em>');
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
html = html.replace(/```([^```]+)```/g, '<pre><code>$1</code></pre>');
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
html = html.replace(/^> (.+)/gim, '<blockquote>$1</blockquote>');
html = html.replace(/^\* (.+)$/gim, '<li>$1</li>');
html = html.replace(/^- (.+)$/gim, '<li>$1</li>');
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
html = html.replace(/^\d+\. (.+)$/gim, '<li>$1</li>');
html = html.replace(/\n\n/g, '</p><p>');
html = html.replace(/^(?!<[hou]|<li|<pre|<blockquote)(.+)$/gim, '<p>$1</p>');
html = html.replace(/<p><\/p>/g, '');
html = html.replace(/<p>(<[hou])/g, '$1');
html = html.replace(/(<\/[hou]l?>)<\/p>/g, '$1');
return html;
}
function showUpdateModal(currentVersion, latestVersion, releaseData) {
const modal = document.getElementById('updateModal');
const currentVersionEl = document.getElementById('currentVersion');
const latestVersionEl = document.getElementById('latestVersion');
const changelogEl = document.getElementById('updateChangelog');
const updateButton = document.getElementById('updateButton');
currentVersionEl.textContent = `v${currentVersion}`;
latestVersionEl.textContent = `v${latestVersion}`;
const changelog = releaseData.body || 'Nincs elérhető változásnapló.';
changelogEl.innerHTML = parseMarkdown(changelog);
updateButton.href = releaseData.html_url;
modal.classList.add('active');
document.getElementById('dismissUpdate').addEventListener('click', closeUpdateModal);
}
function closeUpdateModal() {
const modal = document.getElementById('updateModal');
modal.classList.remove('active');
}

View File

@@ -147,7 +147,8 @@ body {
transition:all 0.3s cubic-bezier(0.4,0,0.2,1);
transform:translateY(0);
}
.lesson-card:hover {
.lesson-card:hover,
.lesson-card.group-hover {
transform:translateY(-4px);
box-shadow:0 8px 12px var(--accent-shadow);
}
@@ -162,6 +163,28 @@ body {
opacity:0.5;
text-decoration:line-through;
}
.lesson-card.lesson-spans-multiple {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
.lesson-card.lesson-continuation.continuation-middle {
border-radius: 0;
}
.lesson-card.lesson-continuation.continuation-end {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.lesson-slot.has-multi-start {
padding-bottom: 0;
}
.lesson-slot.has-continuation {
padding-top: 0;
}
.lesson-subject {
align-self:stretch;
color:var(--text-primary);
@@ -570,7 +593,10 @@ body {
display:flex;
}
.timetable-container {
overflow:hidden;
overflow: hidden;
flex: 1;
display: flex;
flex-direction: column;
}
.timetable-grid {
grid-template-columns:60px 1fr;
@@ -1410,3 +1436,331 @@ to {
transition:gap 0.2s ease;
font-size:clamp(0.875rem,1.5vw,1rem);
}
.timetable-calendar {
display: flex;
flex-direction: column;
background: var(--card-card);
border-radius: 16px;
overflow: hidden;
box-shadow: 0px 1px 2px 0px var(--accent-shadow);
flex: 1;
}
.calendar-header {
display: grid;
grid-template-columns: 80px repeat(5, 1fr);
background: var(--background-0);
border-bottom: 1px solid var(--background-0);
padding: 0;
gap: 0;
}
.time-column-header {
padding: 12px;
border-right: 1px solid var(--background-0);
}
.calendar-day-header {
padding: 12px;
border-right: 1px solid var(--background-0);
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
}
.calendar-day-header:last-child {
border-right: none;
}
.calendar-day-header .day-name {
font-weight: 600;
color: var(--text-primary);
font-size: 14px;
}
.calendar-day-header .day-date {
color: var(--text-secondary);
font-size: 12px;
font-weight: 500;
}
.calendar-notices {
display: grid;
grid-template-columns: 80px repeat(5, 1fr);
border-bottom: 1px solid var(--background-0);
padding: 0;
gap: 0;
background: var(--background);
}
.notices-time-column {
border-right: 1px solid var(--background-0);
}
.notice-day-slot {
padding: 8px;
border-right: 1px solid var(--background-0);
display: flex;
align-items: center;
justify-content: center;
min-height: 60px;
}
.notice-day-slot:last-child {
border-right: none;
}
.notice-day-slot .special-day-notice {
width: 100%;
margin: 0;
}
.calendar-grid {
display: grid;
grid-template-columns: 80px repeat(5, 1fr);
gap: 0;
background: var(--background);
position: relative;
flex: 1;
min-height: 0;
}
.time-column {
border-right: 1px solid var(--background-0);
background: var(--background-0);
position: sticky;
left: 0;
z-index: 10;
display: flex;
flex-direction: column;
height: 100%;
}
.time-marker {
padding: 8px;
text-align: center;
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
border-bottom: 1px solid var(--background);
display: flex;
align-items: flex-start;
justify-content: center;
}
.time-marker:last-child {
border-bottom: none;
}
.calendar-day-column {
position: relative;
border-right: 1px solid var(--background-0);
background: var(--background);
height: 100%;
}
.calendar-day-column:last-child {
border-right: none;
}
.day-background {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
pointer-events: none;
width: 100%;
}
.time-slot-bg {
border-bottom: 1px solid var(--background-0);
flex-shrink: 0;
}
.time-slot-bg:last-child {
border-bottom: none;
}
.lessons-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
}
.calendar-lesson-card {
position: absolute;
left: 4px;
right: 4px;
width: calc(100% - 8px);
display: flex;
flex-direction: column;
padding: 10px;
border-radius: 12px;
background: var(--card-card);
box-shadow: 0px 1px 2px 0px var(--accent-shadow);
border: 1px solid var(--background-0);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: pointer;
overflow: hidden;
z-index: 2;
}
.calendar-lesson-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px var(--accent-shadow);
z-index: 5;
}
.calendar-lesson-card.substituted {
background: var(--warning-card);
border-color: var(--warning-15);
}
.calendar-lesson-card.cancelled {
background: var(--card-translucent);
opacity: 0.6;
}
.calendar-lesson-card.cancelled .lesson-subject,
.calendar-lesson-card.cancelled .lesson-teacher,
.calendar-lesson-card.cancelled .lesson-time-info {
opacity: 0.5;
text-decoration: line-through;
}
.calendar-lesson-card .lesson-subject {
font-weight: 600;
font-size: 13px;
color: var(--text-primary);
line-height: 1.2;
margin-bottom: 2px;
}
.calendar-lesson-card .lesson-teacher {
font-size: 11px;
color: var(--text-secondary);
margin-bottom: 4px;
line-height: 1.2;
}
.calendar-lesson-card .lesson-time-info {
font-size: 11px;
color: var(--text-secondary);
display: flex;
justify-content: space-between;
gap: 4px;
margin-top: auto;
}
.calendar-lesson-card .lesson-room {
background: var(--accent-15);
padding: 2px 6px;
border-radius: 8px;
font-size: 10px;
font-weight: 500;
white-space: nowrap;
}
.calendar-lesson-card.substituted .lesson-room {
background: var(--warning-15);
}
.calendar-lesson-card .lesson-time {
font-weight: 500;
white-space: nowrap;
}
.calendar-lesson-card .lesson-indicators {
position: absolute;
top: 4px;
right: 4px;
display: flex;
gap: 2px;
flex-wrap: wrap;
}
.calendar-lesson-card .lesson-indicator {
display: flex;
align-items: center;
justify-content: center;
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--accent-15);
color: var(--accent-accent);
}
.calendar-lesson-card .lesson-indicator.test-indicator {
background: var(--warning-15);
color: var(--warning-accent);
}
.calendar-lesson-card .lesson-indicator.custom-homework-indicator,
.calendar-lesson-card .lesson-indicator.custom-test-indicator {
background: rgba(0, 0, 0, 0.1);
}
@media (max-width: 768px) {
.calendar-grid {
grid-template-columns: 60px repeat(5, 1fr);
}
.time-column-header {
padding: 8px;
}
.calendar-day-header {
padding: 8px;
}
.calendar-day-header .day-name {
font-size: 12px;
}
.calendar-day-header .day-date {
font-size: 10px;
}
.time-marker {
padding: 4px;
font-size: 10px;
}
.calendar-lesson-card {
padding: 6px;
}
.calendar-lesson-card .lesson-subject {
font-size: 11px;
}
.calendar-lesson-card .lesson-teacher {
font-size: 9px;
}
.calendar-lesson-card .lesson-time-info {
font-size: 9px;
}
.calendar-lesson-card .lesson-indicator {
width: 20px;
height: 20px;
}
.calendar-lesson-card .lesson-indicator img {
width: 14px !important;
height: 14px !important;
}
}

View File

@@ -160,7 +160,7 @@
}
function getLessonKey(lesson) {
return `${lesson.subject}_${lesson.startTime}_${lesson.day}`;
return `${lesson.subject}_${lesson.startTime}_${lesson.date}`;
}
async function updateHomeworkIconsFromCookie() {
@@ -197,8 +197,9 @@
}
const htmlText = await response.text();
const parser = new DOMParser();
const doc = parser.parseFromString(htmlText, 'text/html');
const template = document.createElement('template');
template.innerHTML = htmlText;
const doc = template.content;
const panelBody = doc.querySelector('.panel-body');
const panelFooter = doc.querySelector('.panel-footer');
const teacherInfo = doc.querySelector('.panel-heading');
@@ -312,7 +313,12 @@
}
}
async function loadTestDetailsFromAPI(testId) {
async function loadAllTestDataFromAPI() {
const now = Date.now();
if (cachedTestData && (now - testDataTimestamp) < TEST_DATA_CACHE_DURATION) {
return cachedTestData;
}
try {
const timestamp = Date.now();
const apiUrl = `https://${window.location.hostname}/api/TanuloBejelentettSzamonkeresekApi/GetBejelentettSzamonkeresekGrid?sort=SzamonkeresDatuma-asc~Oraszam-asc&page=1&pageSize=1000&group=&filter=&data=%7B%22RegiSzamonkeresekElrejtese%22%3Afalse%7D&_=${timestamp}`;
@@ -326,15 +332,30 @@
});
if (!response.ok) {
throw new Error(
`Számonkérés API hiba: ${response.status}`,
);
throw new Error(`Számonkérés API hiba: ${response.status}`);
}
const data = await response.json();
const testData = data.Data || [];
cachedTestData = data.Data || [];
testDataTimestamp = now;
return cachedTestData;
} catch (error) {
console.error("Számonkérés adatok betöltési hiba:", error);
return [];
}
}
async function getTestTypeById(testId) {
const testData = await loadAllTestDataFromAPI();
const testDetail = testData.find(test => test.ID === testId.toString());
return testDetail ? testDetail.ErtekelesModNev : null;
}
async function loadTestDetailsFromAPI(testId) {
try {
const testData = await loadAllTestDataFromAPI();
const testDetail = testData.find(test => test.ID === testId.toString());
if (testDetail) {
return {
name: testDetail.SzamonkeresMegnevezes || 'Nincs megnevezés',
@@ -342,7 +363,7 @@
announceDate: testDetail.BejelentesDatuma ? new Date(testDetail.BejelentesDatuma).toLocaleDateString('hu-HU') : 'Nincs dátum'
};
}
return null;
} catch (error) {
console.error("Számonkérés adatok betöltési hiba:", error);
@@ -409,6 +430,7 @@
originalTeacher: "",
room: "",
day: dayIndex,
date: weekDates[dayIndex]?.fullDate || eventDate.toISOString().split('T')[0],
isSubstituted: false,
isCancelled: false,
hasHomework: false,
@@ -445,9 +467,9 @@
"Ismeretlen tantárgy";
if (startTimeStr && subject) {
const isCancelled = event.isElmaradt || event.Elmaradt || event.cancelled || event.isCancelled ||
const isCancelled = event.isElmaradt || event.Elmaradt || event.cancelled || event.isCancelled ||
event.oraType === 6 || (event.title && event.title.toLowerCase().includes('elmarad'));
const lesson = {
startTime: startTimeStr,
endTime: endTimeStr,
@@ -456,14 +478,15 @@
originalTeacher: event.helyettesitoId ? teacher : "",
room: room,
day: dayIndex,
date: weekDates[dayIndex]?.fullDate || eventDate.toISOString().split('T')[0],
isSubstituted: !!event.helyettesitoId,
isCancelled: isCancelled,
hasHomework: event.hasHaziFeladat || false,
testInfo: event.hasBejelentettSzamonkeres
? event.Tema || LanguageManager.t("timetable.test_indicator")
: "",
testId: event.hasBejelentettSzamonkeres && event.BejelentettSzamonkeresIdList && event.BejelentettSzamonkeresIdList.length > 0
? event.BejelentettSzamonkeresIdList[0]
testId: event.hasBejelentettSzamonkeres && event.BejelentettSzamonkeresIdList && event.BejelentettSzamonkeresIdList.length > 0
? event.BejelentettSzamonkeresIdList[0]
: null,
testDetails: "",
homeworkDetails: "",
@@ -496,6 +519,15 @@
}
}
const testTypeMap = {};
const lessonsWithTests = lessons.filter(l => l.testId);
if (lessonsWithTests.length > 0) {
await loadAllTestDataFromAPI();
for (const lesson of lessonsWithTests) {
testTypeMap[lesson.testId] = await getTestTypeById(lesson.testId);
}
}
const times = [...new Set(regularLessons.map((l) => l.startTime))].sort(
(a, b) => {
const timeA = helper.convertTimeToMinutes(a);
@@ -503,6 +535,26 @@
return timeA - timeB;
},
);
function getLessonTimeSlots(lesson, allTimes) {
const startMinutes = helper.convertTimeToMinutes(lesson.startTime);
const endMinutes = helper.convertTimeToMinutes(lesson.endTime);
const slots = [];
for (let i = 0; i < allTimes.length; i++) {
const slotMinutes = helper.convertTimeToMinutes(allTimes[i]);
if (slotMinutes >= startMinutes && slotMinutes < endMinutes) {
slots.push(i);
}
}
return slots.length > 0 ? slots : [allTimes.indexOf(lesson.startTime)];
}
const lessonSlotMap = new Map();
regularLessons.forEach(lesson => {
const slots = getLessonTimeSlots(lesson, times);
lessonSlotMap.set(lesson, slots);
});
const days = [
LanguageManager.t("timetable.monday"),
LanguageManager.t("timetable.tuesday"),
@@ -568,38 +620,84 @@
const dayLessons = regularLessons.filter(
(l) => l.startTime === time && l.day === dayIndex,
);
const continuingInSlot = regularLessons.filter(lesson => {
if (lesson.day !== dayIndex) return false;
const slots = lessonSlotMap.get(lesson);
return slots && slots.includes(timeIndex) && slots[0] !== timeIndex;
});
const hasMultiLesson = dayLessons.some(lesson => {
const slots = lessonSlotMap.get(lesson);
return slots && slots.length > 1;
});
const lastLessonTime = lastLessonTimes[dayIndex];
const isAfterLastLesson = lastLessonTime && helper.convertTimeToMinutes(time) > helper.convertTimeToMinutes(lastLessonTime);
if (dayLessons.length === 0 && isAfterLastLesson) {
if (dayLessons.length === 0 && continuingInSlot.length === 0 && isAfterLastLesson) {
return `<div class="lesson-slot"></div>`;
}
if (continuingInSlot.length > 0) {
return `
<div class="lesson-slot ${dayLessons.length === 0 ? 'empty-slot' : ''}">
${dayLessons.length === 0 ? '<div class="empty-lesson-placeholder"></div>' : ''}
${dayLessons
.map(
(lesson) => `
<div class="lesson-card ${lesson.isSubstituted ? "substituted" : ""}
<div class="lesson-slot has-continuation">
${continuingInSlot.map(lesson => {
const slots = lessonSlotMap.get(lesson);
const isLastSlot = slots[slots.length - 1] === timeIndex;
const lessonGroupId = `${lesson.subject}_${lesson.day}_${lesson.date}_${lesson.startTime}`;
return `
<div class="lesson-card ${lesson.isSubstituted ? "substituted" : ""}
${lesson.isCancelled ? "cancelled" : ""}
${lesson.hasHomework ? "has-homework" : ""}"
lesson-continuation ${isLastSlot ? 'continuation-end' : 'continuation-middle'}"
data-lesson='${JSON.stringify(lesson)}'
data-lesson-id='${lesson.lessonId || ""}'>
<div class="lesson-subject">${lesson.subject}</div>
<div class="lesson-teacher">${lesson.teacher}</div>
data-lesson-id='${lesson.lessonId || ""}'
data-lesson-group='${lessonGroupId}'>
<div class="lesson-bottom">
<div class="lesson-room">${lesson.room}</div>
<div class="lesson-time">${lesson.isCancelled ? LanguageManager.t("timetable.cancelled") : lesson.startTime}</div>
</div>
</div>
`}).join('')}
</div>
`;
}
return `
<div class="lesson-slot ${dayLessons.length === 0 ? 'empty-slot' : ''} ${hasMultiLesson ? 'has-multi-start' : ''}">
${dayLessons.length === 0 ? '<div class="empty-lesson-placeholder"></div>' : ''}
${dayLessons
.map(
(lesson) => {
const slots = lessonSlotMap.get(lesson);
const spansMultiple = slots && slots.length > 1;
const lessonGroupId = `${lesson.subject}_${lesson.day}_${lesson.date}_${lesson.startTime}`;
return `
<div class="lesson-card ${lesson.isSubstituted ? "substituted" : ""}
${lesson.isCancelled ? "cancelled" : ""}
${lesson.hasHomework ? "has-homework" : ""}
${spansMultiple ? "lesson-spans-multiple" : ""}"
data-lesson='${JSON.stringify(lesson)}'
data-lesson-id='${lesson.lessonId || ""}'
data-spans='${slots ? slots.length : 1}'
data-lesson-group='${lessonGroupId}'>
<div class="lesson-subject">${lesson.subject}</div>
<div class="lesson-teacher">${lesson.teacher}</div>
${!spansMultiple ? `<div class="lesson-bottom">
<div class="lesson-room">${lesson.room}</div>
<div class="lesson-time">${lesson.isCancelled ? LanguageManager.t("timetable.cancelled") : lesson.startTime}</div>
</div>` : ''}
${
(() => {
const lessonKey = `${lesson.subject}_${lesson.startTime}_${lesson.day}`;
const hasCustomHomework = customHomework[lessonKey] && customHomework[lessonKey].length > 0;
const hasCustomTests = customTests[lessonKey] && customTests[lessonKey].length > 0;
const lessonKey = `${lesson.subject}_${lesson.startTime}_${lesson.date}`;
const customHomeworkItems = customHomework[lessonKey] || [];
const customTestItems = customTests[lessonKey] || [];
const hasCustomHomework = customHomeworkItems.length > 0;
const hasCustomTests = customTestItems.length > 0;
const allCustomHomeworkCompleted = hasCustomHomework && customHomeworkItems.every(hw => hw.completed);
const allCustomTestsCompleted = hasCustomTests && customTestItems.every(test => test.completed);
const hasAnyIndicators = lesson.hasHomework || lesson.testInfo || hasCustomHomework || hasCustomTests;
return hasAnyIndicators ? `
<div class="lesson-indicators">
${
@@ -613,18 +711,40 @@
}
${
lesson.testInfo
? `
<span class="lesson-indicator test-indicator" title="${LanguageManager.t("timetable.test_indicator")}">
<img src="${chrome.runtime.getURL("icons/assigment.svg")}" alt="Teszt" style="width: 20px; height: 20px;">
? (() => {
const testType = lesson.testId ? testTypeMap[lesson.testId] : null;
const isKontaktOra = testType === "KONTAKT ÓRA";
const isProjektOra = testType === "PROJEKT ÓRA";
let indicatorClass = "test-indicator";
let iconPath = "icons/assigment.svg";
let titleText = LanguageManager.t("timetable.test_indicator");
let altText = "Teszt";
if (isKontaktOra) {
indicatorClass = "homework-indicator";
iconPath = "icons/contact.svg";
titleText = "Kontakt óra";
altText = "Kontakt óra";
} else if (isProjektOra) {
indicatorClass = "homework-indicator";
iconPath = "icons/project.svg";
titleText = "Projekt óra";
altText = "Projekt óra";
}
return `
<span class="lesson-indicator ${indicatorClass}" title="${titleText}">
<img src="${chrome.runtime.getURL(iconPath)}" alt="${altText}" style="width: 20px; height: 20px;">
</span>
`
`;
})()
: ""
}
${
hasCustomHomework
? `
<span class="lesson-indicator custom-homework-indicator" title="Saját házi feladat">
<img src="${chrome.runtime.getURL("icons/homework.svg")}" alt="Saját házi feladat" style="width: 20px; height: 20px; opacity: 0.7;">
<img src="${chrome.runtime.getURL(allCustomHomeworkCompleted ? "icons/pipa.svg" : "icons/homework.svg")}" alt="${allCustomHomeworkCompleted ? 'Megoldott saját házi feladat' : 'Saját házi feladat'}" style="width: 20px; height: 20px; opacity: 0.7;">
</span>
`
: ""
@@ -633,7 +753,7 @@
hasCustomTests
? `
<span class="lesson-indicator custom-test-indicator" title="Saját számonkérés">
<img src="${chrome.runtime.getURL("icons/assigment.svg")}" alt="Saját számonkérés" style="width: 20px; height: 20px; opacity: 0.7;">
<img src="${chrome.runtime.getURL(allCustomTestsCompleted ? "icons/pipa.svg" : "icons/assigment.svg")}" alt="${allCustomTestsCompleted ? 'Megoldott saját számonkérés' : 'Saját számonkérés'}" style="width: 20px; height: 20px; opacity: 0.7;">
</span>
`
: ""
@@ -643,8 +763,8 @@
})()
}
</div>
`,
)
`;
})
.join("")
}
</div>
@@ -887,18 +1007,27 @@
detailsDiv.className = 'homework-details';
const contentP = document.createElement('p');
contentP.innerHTML = `<strong>Feladat:</strong> ${homeworkDetails.content}`;
const contentStrong = document.createElement('strong');
contentStrong.textContent = 'Feladat: ';
contentP.appendChild(contentStrong);
contentP.appendChild(document.createTextNode(homeworkDetails.content));
detailsDiv.appendChild(contentP);
if (homeworkDetails.deadline) {
const deadlineP = document.createElement('p');
deadlineP.innerHTML = `<strong>Határidő:</strong> ${homeworkDetails.deadline}`;
const deadlineStrong = document.createElement('strong');
deadlineStrong.textContent = 'Határidő: ';
deadlineP.appendChild(deadlineStrong);
deadlineP.appendChild(document.createTextNode(homeworkDetails.deadline));
detailsDiv.appendChild(deadlineP);
}
if (homeworkDetails.teacher) {
const teacherP = document.createElement('p');
teacherP.innerHTML = `<strong>Tanár:</strong> ${homeworkDetails.teacher}`;
const teacherStrong = document.createElement('strong');
teacherStrong.textContent = 'Tanár: ';
teacherP.appendChild(teacherStrong);
teacherP.appendChild(document.createTextNode(homeworkDetails.teacher));
detailsDiv.appendChild(teacherP);
}
@@ -908,7 +1037,9 @@
attachmentsDiv.style.marginTop = '1rem';
const attachmentsTitle = document.createElement('p');
attachmentsTitle.innerHTML = '<strong>Csatolmányok:</strong>';
const attachStrong = document.createElement('strong');
attachStrong.textContent = 'Csatolmányok:';
attachmentsTitle.appendChild(attachStrong);
attachmentsTitle.style.marginBottom = '0.5rem';
attachmentsDiv.appendChild(attachmentsTitle);
@@ -1184,74 +1315,92 @@
}
if (lesson.testInfo) {
let testDetails = null;
if (lesson.testId) {
testDetails = await loadTestDetailsFromAPI(lesson.testId);
}
const isKontaktOra = testDetails && testDetails.type === "KONTAKT ÓRA";
const isProjektOra = testDetails && testDetails.type === "PROJEKT ÓRA";
const isSpecialType = isKontaktOra || isProjektOra;
const testSection = document.createElement('div');
testSection.className = 'modal-section test-section';
testSection.className = isSpecialType ? 'modal-section homework-section' : 'modal-section test-section';
const testH4 = document.createElement('h4');
const testIcon = document.createElement('img');
testIcon.src = chrome.runtime.getURL('icons/assigment.svg');
testIcon.alt = 'Teszt';
let iconPath = 'icons/assigment.svg';
let sectionTitle = LanguageManager.t('timetable.test_indicator');
let altText = 'Teszt';
if (isKontaktOra) {
iconPath = 'icons/contact.svg';
sectionTitle = 'Kontakt óra';
altText = 'Kontakt óra';
} else if (isProjektOra) {
iconPath = 'icons/project.svg';
sectionTitle = 'Projekt óra';
altText = 'Projekt óra';
}
testIcon.src = chrome.runtime.getURL(iconPath);
testIcon.alt = altText;
testIcon.style.width = '20px';
testIcon.style.height = '20px';
testH4.appendChild(testIcon);
testH4.appendChild(document.createTextNode(LanguageManager.t('timetable.test_indicator')));
testH4.appendChild(document.createTextNode(sectionTitle));
if (isSpecialType) {
testH4.style.color = 'var(--accent-accent)';
}
const testContent = document.createElement('div');
testContent.className = 'test-content';
if (lesson.testId) {
const loadingDiv = document.createElement('div');
loadingDiv.className = 'test-details-loading';
loadingDiv.textContent = 'Részletek betöltése...';
testContent.appendChild(loadingDiv);
if (testDetails) {
const detailsDiv = document.createElement('div');
detailsDiv.className = 'test-details';
loadTestDetailsFromAPI(lesson.testId).then(testDetails => {
loadingDiv.remove();
if (testDetails) {
const detailsDiv = document.createElement('div');
detailsDiv.className = 'test-details';
const nameP = document.createElement('p');
nameP.innerHTML = `<strong>Megnevezés:</strong> ${testDetails.name}`;
detailsDiv.appendChild(nameP);
const typeP = document.createElement('p');
typeP.innerHTML = `<strong>Típus:</strong> ${testDetails.type}`;
detailsDiv.appendChild(typeP);
const dateP = document.createElement('p');
dateP.innerHTML = `<strong>Bejelentés dátuma:</strong> ${testDetails.announceDate}`;
detailsDiv.appendChild(dateP);
testContent.appendChild(detailsDiv);
} else {
const errorP = document.createElement('p');
errorP.className = 'test-details-error';
errorP.textContent = 'Nem sikerült betölteni a számonkérés részleteit.';
testContent.appendChild(errorP);
}
}).catch(error => {
loadingDiv.remove();
const errorP = document.createElement('p');
errorP.className = 'test-details-error';
errorP.textContent = 'Hiba történt a számonkérés részletek betöltése során.';
testContent.appendChild(errorP);
});
const nameP = document.createElement('p');
const nameStrong = document.createElement('strong');
nameStrong.textContent = 'Megnevezés: ';
nameP.appendChild(nameStrong);
nameP.appendChild(document.createTextNode(testDetails.name));
detailsDiv.appendChild(nameP);
const typeP = document.createElement('p');
const typeStrong = document.createElement('strong');
typeStrong.textContent = 'Típus: ';
typeP.appendChild(typeStrong);
typeP.appendChild(document.createTextNode(testDetails.type));
detailsDiv.appendChild(typeP);
const dateP = document.createElement('p');
const dateStrong = document.createElement('strong');
dateStrong.textContent = 'Bejelentés dátuma: ';
dateP.appendChild(dateStrong);
dateP.appendChild(document.createTextNode(testDetails.announceDate));
detailsDiv.appendChild(dateP);
testContent.appendChild(detailsDiv);
} else if (lesson.testId) {
const errorP = document.createElement('p');
errorP.className = 'test-details-error';
errorP.textContent = 'Nem sikerült betölteni a számonkérés részleteit.';
testContent.appendChild(errorP);
}
const lessonKey = getLessonKey(lesson);
const customTests = await getCustomTests();
const customTestItems = customTests[lessonKey] || [];
if (customTestItems.length > 0) {
const customTestsDiv = document.createElement('div');
customTestsDiv.className = 'custom-tests-in-section';
customTestsDiv.style.marginTop = '1rem';
customTestsDiv.style.paddingTop = '1rem';
customTestsDiv.style.borderTop = '1px solid var(--background-0)';
const customTestsTitle = document.createElement('h5');
customTestsTitle.textContent = 'Saját számonkérések:';
customTestsTitle.style.fontSize = '14px';
@@ -1259,10 +1408,10 @@
customTestsTitle.style.color = 'var(--warning-accent)';
customTestsTitle.style.marginBottom = '0.5rem';
customTestsDiv.appendChild(customTestsTitle);
const customTestsList = document.createElement('div');
customTestsList.className = 'custom-tests-list-integrated';
customTestItems.forEach(test => {
const testItem = document.createElement('div');
testItem.className = `custom-test-item-integrated ${test.completed ? 'completed' : ''}`;
@@ -1274,7 +1423,7 @@
testItem.style.background = 'var(--background)';
testItem.style.borderRadius = '6px';
testItem.style.border = '1px solid var(--background-0)';
const testText = document.createElement('span');
testText.className = 'test-text-integrated';
testText.textContent = test.text;
@@ -1284,12 +1433,12 @@
testText.style.textDecoration = 'line-through';
testText.style.opacity = '0.6';
}
const testActions = document.createElement('div');
testActions.className = 'test-actions-integrated';
testActions.style.display = 'flex';
testActions.style.gap = '0.5rem';
const completeBtn = document.createElement('button');
completeBtn.className = 'test-complete-btn-integrated';
completeBtn.title = test.completed ? 'Megoldva - kattints a visszavonáshoz' : 'Megoldottként jelöl';
@@ -1301,7 +1450,7 @@
completeBtn.style.display = 'flex';
completeBtn.style.alignItems = 'center';
completeBtn.style.justifyContent = 'center';
const completeIcon = document.createElement('img');
completeIcon.src = chrome.runtime.getURL('icons/pipa.svg');
completeIcon.alt = 'Megoldva';
@@ -1314,7 +1463,7 @@
completeIcon.style.opacity = '0.5';
}
completeBtn.appendChild(completeIcon);
const deleteBtn = document.createElement('button');
deleteBtn.className = 'test-delete-btn-integrated';
deleteBtn.title = 'Törlés';
@@ -1326,7 +1475,7 @@
deleteBtn.style.display = 'flex';
deleteBtn.style.alignItems = 'center';
deleteBtn.style.justifyContent = 'center';
const deleteIcon = document.createElement('img');
deleteIcon.src = chrome.runtime.getURL('icons/delete.svg');
deleteIcon.alt = 'Törlés';
@@ -1334,7 +1483,7 @@
deleteIcon.style.height = '16px';
deleteIcon.style.opacity = '0.5';
deleteBtn.appendChild(deleteIcon);
completeBtn.addEventListener('click', async () => {
const newCompleted = await toggleCustomTestCompletion(lessonKey, test.id);
if (newCompleted) {
@@ -1351,30 +1500,30 @@
completeBtn.title = 'Megoldottként jelöl';
}
});
deleteBtn.addEventListener('click', async () => {
if (confirm('Biztosan törölni szeretnéd ezt a számonkérést?')) {
await removeCustomTest(lessonKey, test.id);
testItem.remove();
}
});
testActions.appendChild(completeBtn);
testActions.appendChild(deleteBtn);
testItem.appendChild(testText);
testItem.appendChild(testActions);
customTestsList.appendChild(testItem);
});
customTestsDiv.appendChild(customTestsList);
testContent.appendChild(customTestsDiv);
}
testSection.appendChild(testH4);
testSection.appendChild(testContent);
body.appendChild(testSection);
}
const lessonKey = getLessonKey(lesson);
const customHomework = await getCustomHomework();
@@ -1632,7 +1781,7 @@
modalContent.appendChild(header);
modalContent.appendChild(body);
modal.innerHTML = '';
helper.clearElement(modal);
modal.appendChild(modalContent);
document.body.appendChild(modal);
@@ -1703,11 +1852,11 @@
if (timetableGrid) {
const newContent = await generateTimeGrid(allLessons, weekDates);
timetableGrid.innerHTML = '';
helper.clearElement(timetableGrid);
const parser1 = new DOMParser();
const doc = parser1.parseFromString(`<div>${newContent}</div>`, 'text/html');
const tempDiv = doc.querySelector('div');
const template = document.createElement('template');
template.innerHTML = `<div>${newContent}</div>`;
const tempDiv = template.content.querySelector('div');
while (tempDiv.firstChild) {
timetableGrid.appendChild(tempDiv.firstChild);
}
@@ -1869,6 +2018,24 @@
const lessonData = JSON.parse(card.dataset.lesson);
await showLessonModal(lessonData);
});
card.addEventListener("mouseenter", () => {
const groupId = card.dataset.lessonGroup;
if (groupId) {
document.querySelectorAll(`[data-lesson-group="${groupId}"]`).forEach(relatedCard => {
relatedCard.classList.add('group-hover');
});
}
});
card.addEventListener("mouseleave", () => {
const groupId = card.dataset.lessonGroup;
if (groupId) {
document.querySelectorAll(`[data-lesson-group="${groupId}"]`).forEach(relatedCard => {
relatedCard.classList.remove('group-hover');
});
}
});
});
}
@@ -2186,7 +2353,7 @@
}
modalGrid.innerHTML = '';
helper.clearElement(modalGrid);
allWeeks.forEach((week) => {
const weekCell = document.createElement('div');
weekCell.className = `week-cell ${week.selected ? 'selected' : ''} ${week.selected ? 'current-week' : ''}`;
@@ -2274,7 +2441,7 @@
const weekGrid = document.getElementById("week-grid");
if (weekGrid) {
weekGrid.innerHTML = '';
helper.clearElement(weekGrid);
newWeekOptions.forEach((opt) => {
const weekCell = document.createElement('div');
weekCell.className = `week-cell ${opt.selected ? 'selected' : ''}`;
@@ -2321,7 +2488,7 @@
};
document.body.innerHTML = '';
helper.clearElement(document.body);
const kretaContainer = document.createElement('div');
@@ -2330,9 +2497,9 @@
const headerDiv = document.createElement('div');
const parser2 = new DOMParser();
const headerDoc = parser2.parseFromString(await createTemplate.header(), 'text/html');
const headerContent = headerDoc.body;
const template = document.createElement('template');
template.innerHTML = await createTemplate.header();
const headerContent = template.content;
while (headerContent.firstChild) {
headerDiv.appendChild(headerContent.firstChild);
}
@@ -2480,9 +2647,9 @@
const gridContent = await generateTimeGrid(data.lessons, data.weekDates);
const parser3 = new DOMParser();
const doc = parser3.parseFromString(`<div>${gridContent}</div>`, 'text/html');
const tempDiv = doc.querySelector('div');
const template3 = document.createElement('template');
template3.innerHTML = `<div>${gridContent}</div>`;
const tempDiv = template3.content.querySelector('div');
while (tempDiv.firstChild) {
timetableGrid.appendChild(tempDiv.firstChild);
}
@@ -2541,7 +2708,7 @@
}
weekDisplay.innerHTML = '';
helper.clearElement(weekDisplay);
visibleWeeks.forEach((weekNum, index) => {
const isSelected = index === 2;
const isCurrent = weekNum === currentWeekNumber;
@@ -2637,12 +2804,12 @@
const timetableContainer = document.querySelector(".timetable-grid");
if (timetableContainer) {
timetableContainer.innerHTML = '';
helper.clearElement(timetableContainer);
const gridContent = await generateTimeGrid(lessons, weekDates);
const parser2 = new DOMParser();
const doc = parser2.parseFromString(`<div>${gridContent}</div>`, 'text/html');
const tempDiv = doc.querySelector('div');
const template2 = document.createElement('template');
template2.innerHTML = `<div>${gridContent}</div>`;
const tempDiv = template2.content.querySelector('div');
while (tempDiv.firstChild) {
timetableContainer.appendChild(tempDiv.firstChild);
}
@@ -2670,7 +2837,7 @@
}
modalGrid.innerHTML = '';
helper.clearElement(modalGrid);
allWeeks.forEach((weekNumber) => {
const isSelected = weekNumber === selectedWeekNumber;
const isCurrent = weekNumber === currentWeekNumber;

View File

@@ -34,6 +34,11 @@ chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
sendResponse({ success: true });
break;
case 'download_attachment':
const downloadResult = await handleDownloadAttachment(request.azonosito, request.fileName);
sendResponse(downloadResult);
break;
default:
console.warn('[Background] Unknown action:', request.action);
sendResponse({ success: false, error: 'Unknown action' });
@@ -79,7 +84,7 @@ async function handleStorageClear() {
try {
const allData = await chrome.storage.sync.get(null);
const firkaKeys = Object.keys(allData).filter(key => key.startsWith('firka_'));
if (firkaKeys.length > 0) {
await chrome.storage.sync.remove(firkaKeys);
}
@@ -89,6 +94,82 @@ async function handleStorageClear() {
}
}
async function handleDownloadAttachment(azonosito, fileName) {
try {
const apiUrl = `https://eugyintezes.e-kreta.hu/api/v1/dokumentumok/uzenetek/${azonosito}`;
const redirectResponse = await fetch(apiUrl, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json, text/plain, */*',
'x-csrf': '1',
'x-uzenet-json-formatum': 'CamelCase',
'x-uzenet-lokalizacio': 'hu-HU',
'x-uzenet-verzio-szam': '1.2.3'
},
redirect: 'manual'
});
let fileUrl;
if (redirectResponse.type === 'opaqueredirect' || redirectResponse.status === 0) {
const followResponse = await fetch(apiUrl, {
method: 'GET',
credentials: 'include',
headers: {
'Accept': 'application/json, text/plain, */*',
'x-csrf': '1',
'x-uzenet-json-formatum': 'CamelCase',
'x-uzenet-lokalizacio': 'hu-HU',
'x-uzenet-verzio-szam': '1.2.3'
},
redirect: 'follow'
});
if (followResponse.ok) {
const blob = await followResponse.blob();
const base64 = await blobToBase64(blob);
return { success: true, data: base64, fileName: fileName };
}
} else if (redirectResponse.status === 302 || redirectResponse.status === 301) {
fileUrl = redirectResponse.headers.get('location');
}
if (fileUrl) {
const fileResponse = await fetch(fileUrl, {
method: 'GET',
headers: {
'Accept': 'application/json, text/plain, */*',
'x-csrf': '1',
'x-uzenet-json-formatum': 'CamelCase',
'x-uzenet-lokalizacio': 'hu-HU',
'x-uzenet-verzio-szam': '1.2.3'
}
});
if (fileResponse.ok) {
const blob = await fileResponse.blob();
const base64 = await blobToBase64(blob);
return { success: true, data: base64, fileName: fileName };
}
}
return { success: false, error: 'Nem sikerült letölteni a mellékletet.' };
} catch (error) {
console.error('[Background] Attachment download error:', error);
return { success: false, error: error.message };
}
}
function blobToBase64(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
chrome.storage.onChanged.addListener((changes, namespace) => {
if (namespace === 'sync') {
const firkaChanges = Object.keys(changes).filter(key => key.startsWith('firka_'));

View File

@@ -37,4 +37,28 @@ const helper = {
const [hours, minutes] = timeStr.split(":").map(Number);
return hours * 60 + minutes;
},
createElementFromHTML(htmlString) {
const template = document.createElement('template');
template.innerHTML = htmlString.trim();
return template.content;
},
setTextContent(element, text) {
element.textContent = text;
},
clearElement(element) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
},
appendChildren(parent, children) {
if (Array.isArray(children)) {
children.forEach(child => parent.appendChild(child));
} else {
parent.appendChild(children);
}
}
};