33 Commits
1.4.3b ... main

Author SHA1 Message Date
Zan
6a5ecadbdc new web 2026-03-29 16:18:52 +02:00
Zan
1bdc8b5692 Few fixes 2026-03-28 23:55:27 +01:00
Zan
aea3b20ff9 fix light text in absences 2026-03-11 18:16:08 +01:00
Zan
ee42a064d2 update build 2026-02-23 20:58:45 +01:00
Zan
89296804be Profile page rework 2026-01-19 21:47:12 +01:00
Zan
29566f4fae few fixes to settings 2026-01-19 20:55:54 +01:00
Zan
93c9c2d307 Update timetable.js 2026-01-15 19:24:12 +01:00
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
Zan
0dbb3402a8 Page settings 2025-12-08 14:19:36 +01:00
Zan
64c54171cb setup page 2025-12-08 13:13:50 +01:00
Zan
3d399ee8be logo fix 2025-12-08 13:13:42 +01:00
Zan
431103d2ff login text fix 2025-12-08 13:07:09 +01:00
51 changed files with 6601 additions and 2526 deletions

View File

@@ -1,71 +1,82 @@
name: Package and Release Extension
name: Build and Release
on:
workflow_dispatch:
push:
branches:
- test
inputs:
version:
description: 'Release version (pl. v1.0.0)'
required: true
default: 'v1.0.0'
jobs:
build-and-release:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get current date
id: date
- name: Set up build directories
run: |
echo "DATE=$(date +'%Y.%m.%d. %H:%M')" >> $GITHUB_ENV
echo "DATE_FOR_ZIP=$(date +'%Y%m%d-%H%M')" >> $GITHUB_ENV
mkdir -p build/chrome
mkdir -p build/firefox
- name: Create ZIP file
- name: Copy files Chrome
run: |
zip -r "pre-firxa-${{ env.DATE_FOR_ZIP }}.zip" . -x "*.git*" "*.github*" "*.idea*"
rsync -av \
--exclude='.git' \
--exclude='.gitea' \
--exclude='README*' \
--exclude='readme*' \
--exclude='LICENSE*' \
--exclude='license*' \
--exclude='*.md' \
--exclude='build/' \
. build/chrome/
- name: Delete previous pre-release
uses: dev-drprasad/delete-tag-and-release@v0.2.1
with:
delete_release: true
tag_name: pre-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
- name: Copy files Firefox
run: |
rsync -av \
--exclude='.git' \
--exclude='.gitea' \
--exclude='README*' \
--exclude='readme*' \
--exclude='LICENSE*' \
--exclude='license*' \
--exclude='*.md' \
--exclude='build/' \
. build/firefox/
- name: Create new pre-release
id: create_release
uses: softprops/action-gh-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Prepare Chrome build
run: |
rm -f build/chrome/manifest_fox.json
- name: Prepare Firefox build
run: |
rm -f build/firefox/manifest.json
mv build/firefox/manifest_fox.json build/firefox/manifest.json
- name: Create ZIP archives
run: |
cd build/chrome
zip -r ../../firka-extension-chrome-${{ github.event.inputs.version }}.zip .
cd ../firefox
zip -r ../../firka-extension-firefox-${{ github.event.inputs.version }}.zip .
- name: Create Release
uses: actions/gitea-release@v1
with:
tag_name: pre-release
release_name: Fejlesztői build
token: ${{ secrets.GITEA_TOKEN }}
tag_name: ${{ github.event.inputs.version }}
release_name: ${{ github.event.inputs.version }}
body: |
Ez egy kiadás előtti build, amely minden egyes commit után frissül!
A build automatikusan készült ekkor: ${{ env.DATE }}
## Firka Extension ${{ github.event.inputs.version }}
### Letöltések
- **Chrome / Chromium** `firka-extension-chrome-${{ github.event.inputs.version }}.zip`
- **Firefox** `firka-extension-firefox-${{ github.event.inputs.version }}.zip`
files: |
firka-extension-chrome-${{ github.event.inputs.version }}.zip
firka-extension-firefox-${{ github.event.inputs.version }}.zip
draft: false
prerelease: true
- name: Upload ZIP to release
if: steps.create_release.outputs.upload_url != ''
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./pre-firxa-${{ env.DATE_FOR_ZIP }}.zip
asset_name: pre-firxa-${{ env.DATE_FOR_ZIP }}.zip
asset_content_type: application/zip
check-links:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Link Checker
id: lychee
uses: lycheeverse/lychee-action@v2
with:
fail: false
prerelease: false

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: var(--text-primary);
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: var(--text-primary);
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

@@ -37,6 +37,8 @@ h2 {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr));
gap: 20px;
justify-content: center;
justify-items: center;
}
.widget-card {
@@ -50,6 +52,7 @@ h2 {
height: 100%;
border: none;
min-height: 400px;
width: 100%;
}
.widget-header {
@@ -222,7 +225,6 @@ h2 {
max-width: 200px;
}
/* Mobile view: author below content */
@media (max-width: 768px) {
.news-item .widget-row {
flex-direction: column;
@@ -257,7 +259,6 @@ h2 {
}
}
/* Desktop view: limit author width and wrap text */
@media (min-width: 769px) {
.news-author {
max-width: 180px;

View File

@@ -291,8 +291,9 @@ class DashboardDataManager {
}
class DashboardRenderer {
constructor(data) {
constructor(data, settings = {}) {
this.baseData = data;
this.settings = settings;
}
async init() {
@@ -315,14 +316,31 @@ class DashboardRenderer {
}
generateMainContent() {
const cards = [];
if (!this.settings.hideGrades) {
cards.push(this.createGradeCard());
}
if (!this.settings.hideAbsences) {
cards.push(this.createAbsenceCard());
}
if (!this.settings.hideNotes) {
cards.push(this.createNoteCard());
}
if (!this.settings.hideExams) {
cards.push(this.createExamCard());
}
cards.push(this.createNewsCard());
if (cards.length === 1) {
cards.unshift(this.createGradeCard());
}
return `
<main class="kreta-main">
<div class="grid-container">
${this.createGradeCard()}
${this.createAbsenceCard()}
${this.createNoteCard()}
${this.createExamCard()}
${this.createNewsCard()}
${cards.join('')}
</div>
</main>
`;
@@ -481,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);
}
@@ -513,6 +531,26 @@ class DashboardApplication {
this.init();
}
async loadBulletinSettings() {
try {
const settings = await storageManager.get("pageSettings_bulletin", {});
return {
hideGrades: settings.hideGrades || false,
hideAbsences: settings.hideAbsences || false,
hideNotes: settings.hideNotes || false,
hideExams: settings.hideExams || false
};
} catch (error) {
console.error("Error loading bulletin settings:", error);
return {
hideGrades: false,
hideAbsences: false,
hideNotes: false,
hideExams: false
};
}
}
async init() {
if (!window.location.href.includes("/Intezmeny/Faliujsag")) {
return;
@@ -575,8 +613,9 @@ class DashboardApplication {
try {
const dataManager = new DashboardDataManager();
const dashboardData = await dataManager.extractAllData();
const bulletinSettings = await this.loadBulletinSettings();
const renderer = new DashboardRenderer(dashboardData);
const renderer = new DashboardRenderer(dashboardData, bulletinSettings);
await renderer.render();
} catch (error) {
console.error("Error initializing dashboard:", error);

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,8 @@
getAvailableLanguages: () => [
{ code: "hu", name: "Magyar" },
{ code: "en", name: "English" },
{ code: "de", name: "Deutsch" },
],
init: initializeLanguage,
};
})();

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

@@ -197,7 +197,7 @@
}
.dropdown-item:hover img {
filter: none;
filter: brightness(0) saturate(100%) invert(25%) sepia(50%) saturate(2000%) hue-rotate(90deg) brightness(90%) contrast(95%);
}
@keyframes dropdownShow {
@@ -418,7 +418,7 @@
}
.mobile-dropdown-item:hover img {
filter: none;
filter: brightness(0) saturate(100%) invert(25%) sepia(50%) saturate(2000%) hue-rotate(90deg) brightness(90%) contrast(95%);
}
body {

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

@@ -458,6 +458,9 @@ to {
gap:1rem;
padding:1rem;
}
.grade-distribution.centered {
justify-content:center;
}
.grade-count {
display:flex;
align-items:center;

View File

@@ -1,6 +1,25 @@
(() => {
let gradesSettings = {
hideChart: false,
hideClassAverage: false
};
async function loadGradesSettings() {
try {
const settings = await storageManager.get("pageSettings_grades", {});
gradesSettings = {
hideChart: settings.hideChart || false,
hideClassAverage: settings.hideClassAverage || false
};
} catch (error) {
console.error("Error loading grades settings:", error);
}
}
async function transformGradesPage() {
try {
await loadGradesSettings();
const tanuloIdElement = document.querySelector("#TanuloId");
const tanuloId = tanuloIdElement ? tanuloIdElement.value : "772481";
@@ -9,28 +28,31 @@
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();
const script = document.createElement("script");
script.src = chrome.runtime.getURL("grades/chart.js");
document.head.appendChild(script);
if (!gradesSettings.hideChart) {
const script = document.createElement("script");
script.src = chrome.runtime.getURL("grades/chart.js");
document.head.appendChild(script);
script.onload = () => {
setupGradesChart(gradesData.subjects);
};
script.onload = () => {
setupGradesChart(gradesData.subjects);
};
}
setupEventListeners();
setupGradesListScrolling();
@@ -44,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: {
@@ -59,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);
@@ -390,12 +433,16 @@
schoolNameFull = `${data.schoolInfo.id} - ${data.schoolInfo.name}`;
shortenedSchoolName = helper.shortenSchoolName(schoolNameFull);
const showClassAverage = !gradesSettings.hideClassAverage && classAverage > 0;
const showChart = !gradesSettings.hideChart;
return `
<div class="kreta-container">
${await createTemplate.header()}
<main class="kreta-main">
<div class="grades-overview">
${showChart ? `
<div class="overall-averages card">
<div class="chart-header">
<div class="chart-title">${LanguageManager.t("grades.chart_title")} (${totalGrades}db)</div>
@@ -404,7 +451,7 @@
<span class="average-value ${studentAverage < 2 && studentAverage > 0 ? "warning" : ""}">${studentAverage > 0 ? studentAverage.toFixed(2) : "-"}</span>
</div>
${
classAverage > 0
showClassAverage
? `
<div class="average-circle class-average" data-grade="${classGradeLevel}">
<span class="average-value">${classAverage.toFixed(2)}</span>
@@ -430,6 +477,39 @@
.join("")}
</div>
</div>
` : `
<div class="overall-averages card">
<div class="chart-header">
<div class="chart-title">${LanguageManager.t("grades.chart_title")} (${totalGrades}db)</div>
<div class="chart-averages">
<div class="average-circle my-average" data-grade="${studentGradeLevel}">
<span class="average-value ${studentAverage < 2 && studentAverage > 0 ? "warning" : ""}">${studentAverage > 0 ? studentAverage.toFixed(2) : "-"}</span>
</div>
${
showClassAverage
? `
<div class="average-circle class-average" data-grade="${classGradeLevel}">
<span class="average-value">${classAverage.toFixed(2)}</span>
</div>
`
: ""
}
</div>
</div>
<div class="grade-distribution centered">
${Object.entries(gradeDistribution)
.map(
([grade, count]) => `
<div class="grade-count grade-${grade}">
<span class="grade-value">${grade}</span>
<span class="grade-amount">${count}</span>
</div>
`,
)
.join("")}
</div>
</div>
`}
${
yearEndGrades.length > 0
? `
@@ -635,6 +715,7 @@
.reverse();
const myGrade = Math.floor(subject.average) || 0;
const classGrade = Math.floor(subject.classAverage) || 0;
const showClassAvg = !gradesSettings.hideClassAverage && subject.classAverage > 0;
return `
<div class="subject-card card">
@@ -647,7 +728,7 @@
<span class="average-value">${subject.average > 0 ? subject.average.toFixed(2) : "-"}</span>
</div>
${
subject.classAverage > 0
showClassAvg
? `
<div class="average-circle class-average" data-grade="${classGrade}">
<span class="average-value">${subject.classAverage.toFixed(2)}</span>

596
i18n/de.json Normal file
View File

@@ -0,0 +1,596 @@
{
"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_my_data": "Meine Daten",
"tab_settings": "Einstellungen",
"tab_password": "Passwort ändern",
"tab_security": "Sicherheitseinstellungen",
"tab_contacts": "Kontakte",
"loading_data": "Daten werden geladen...",
"loading_error": "Fehler beim Laden der Daten.",
"my_data_title": "Meine persönlichen Daten",
"personal_data": "Persönliche Daten",
"contact_data": "Kontaktdaten",
"address_data": "Adressdaten",
"bank_account_data": "Bankverbindung",
"student_card_data": "Schülerausweis Daten",
"default_address": "Standard",
"password_change_unavailable": "Die Passwortänderungsfunktion ist derzeit nicht verfügbar.",
"security_settings_info": "Um Sicherheitseinstellungen zu verwalten, klicken Sie auf die Schaltfläche unten:",
"open_security_settings": "Sicherheitseinstellungen öffnen",
"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.",
"field_familyName": "Familienname",
"field_firstName": "Vorname",
"field_birthFamilyName": "Geburts-Familienname",
"field_birthFirstName": "Geburts-Vorname",
"field_motherFamilyName": "Geburts-Familienname der Mutter",
"field_motherFirstName": "Geburts-Vorname der Mutter",
"field_birthDate": "Geburtsdatum",
"field_birthPlace": "Geburtsort",
"field_birthCountry": "Geburtsland",
"field_motherTongue": "Muttersprache",
"field_citizenship": "Staatsangehörigkeit",
"field_classTeacher": "Klassenlehrer",
"field_className": "Klasse",
"field_classroom": "Klassenzimmer",
"field_username": "Benutzername",
"field_phone": "Telefonnummer",
"field_email": "Benachrichtigungs-E-Mail-Adresse",
"field_accountNumber": "Bankkontonummer",
"field_bankName": "Bankname",
"field_accountOwner": "Kontoinhaber",
"field_cardNumber": "Kartennummer",
"field_taxId": "Steuer-ID",
"field_studentCardNumber": "Studentenausweisnummer",
"field_socialSecurityNumber": "Sozialversicherungsnummer"
},
"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

@@ -5,8 +5,47 @@
},
"settings": {
"title": "Settings",
"appearance": "Appearance",
"theme": "Theme",
"language": "Language",
"tabs": {
"appearance": "Appearance",
"settings": "Settings",
"about": "About"
},
"page_settings": {
"title": "Page Settings",
"current_page": "Current page",
"no_settings": "No custom settings available for this page.",
"login": {
"hide_system_message": "Hide system message",
"hide_system_message_desc": "Hide the system message shown on the login page",
"hide_school_info": "Hide school name and ID",
"hide_school_info_desc": "The school name and KRÉTA ID will not be displayed"
},
"roleselect": {
"auto_redirect": "Auto redirect",
"auto_redirect_desc": "Automatically redirect to the gradebook",
"hide_school_info": "Hide school and name",
"hide_school_info_desc": "The school name and user name will not be displayed"
},
"bulletin": {
"hide_grades": "Hide grades",
"hide_grades_desc": "Hide the grades card",
"hide_absences": "Hide absences",
"hide_absences_desc": "Hide the absences card",
"hide_notes": "Hide notes",
"hide_notes_desc": "Hide the notes card",
"hide_exams": "Hide announced exams",
"hide_exams_desc": "Hide the announced exams card"
},
"grades": {
"hide_chart": "Hide chart",
"hide_chart_desc": "Hide the grades chart",
"hide_class_average": "Hide class average",
"hide_class_average_desc": "Hide class average values"
}
},
"themes": {
"light_green": "Light Green",
"dark_green": "Dark Green",
@@ -34,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",
@@ -55,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": {
@@ -217,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",
@@ -226,10 +278,23 @@
"school": "School",
"student_id": "Student ID",
"settings_title": "Profile settings",
"tab_my_data": "My Data",
"tab_settings": "Settings",
"tab_password": "Change password",
"tab_security": "Security settings",
"tab_contacts": "Contact information",
"loading_data": "Loading data...",
"loading_error": "An error occurred while loading the data.",
"my_data_title": "My Personal Data",
"personal_data": "Personal Information",
"contact_data": "Contact Information",
"address_data": "Address Data",
"bank_account_data": "Bank Account Data",
"student_card_data": "Student Card Data",
"default_address": "Default",
"password_change_unavailable": "The password change function is currently unavailable.",
"security_settings_info": "To manage security settings, click the button below:",
"open_security_settings": "Open Security Settings",
"two_factor_description": "To use two-factor authentication, install a time-based one-time password (TOTP) application:",
"android": "Android",
"iphone": "iPhone",
@@ -263,7 +328,31 @@
"passwords_not_match": "New passwords do not match!",
"password_too_short": "New password must be at least 8 characters long!",
"password_changed": "Password changed successfully!",
"password_change_error": "An error occurred while changing password. Please try again later."
"password_change_error": "An error occurred while changing password. Please try again later.",
"field_familyName": "Family name",
"field_firstName": "First name",
"field_birthFamilyName": "Birth family name",
"field_birthFirstName": "Birth first name",
"field_motherFamilyName": "Mother's birth family name",
"field_motherFirstName": "Mother's birth first name",
"field_birthDate": "Date of birth",
"field_birthPlace": "Place of birth",
"field_birthCountry": "Country of birth",
"field_motherTongue": "Mother tongue",
"field_citizenship": "Citizenship",
"field_classTeacher": "Class teacher",
"field_className": "Class",
"field_classroom": "Classroom",
"field_username": "Username",
"field_phone": "Phone number",
"field_email": "Notification email address",
"field_accountNumber": "Bank account number",
"field_bankName": "Bank name",
"field_accountOwner": "Account owner's name",
"field_cardNumber": "Card number",
"field_taxId": "Tax ID",
"field_studentCardNumber": "Student card number",
"field_socialSecurityNumber": "Social security number"
},
"login": {
"title": "Login",
@@ -309,6 +398,35 @@
"success": "Successfully logged out!",
"continue": "Continue"
},
"setup": {
"welcome": "Set up the extension in a few simple steps",
"steps": {
"theme": "Theme",
"language": "Language",
"finish": "Done"
},
"theme": {
"title": "Choose a theme",
"description": "Select the appearance that suits you best"
},
"language": {
"title": "Choose a language",
"description": "Select the language you want to use"
},
"finish": {
"title": "All set!",
"description": "Settings saved successfully. Let's start learning!",
"about": "About",
"about_desc": "Learn more about the project",
"support": "Support",
"support_desc": "Support the development",
"github": "GitHub",
"github_desc": "View the source code",
"discord": "Discord",
"discord_desc": "Join the community",
"start": "Get Started"
}
},
"common": {
"save": "Save",
"cancel": "Cancel",
@@ -340,8 +458,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

@@ -5,8 +5,47 @@
},
"settings": {
"title": "Beállítások",
"appearance": "Megjelenés",
"theme": "Téma",
"language": "Nyelv",
"tabs": {
"appearance": "Megjelenés",
"settings": "Beállítások",
"about": "Névjegy"
},
"page_settings": {
"title": "Oldal beállítások",
"current_page": "Aktuális oldal",
"no_settings": "Ehhez az oldalhoz nincsenek egyéni beállítások.",
"login": {
"hide_system_message": "Rendszerüzenet elrejtése",
"hide_system_message_desc": "A bejelentkezési oldalon megjelenő rendszerüzenet elrejtése",
"hide_school_info": "Iskola nevének és azonosítójának elrejtése",
"hide_school_info_desc": "Az iskola neve és KRÉTA azonosítója nem jelenik meg"
},
"roleselect": {
"auto_redirect": "Automatikus továbblépés",
"auto_redirect_desc": "Automatikusan átirányít az ellenőrzőkönyvre",
"hide_school_info": "Iskola és név elrejtése",
"hide_school_info_desc": "Az iskola neve és a felhasználó neve nem jelenik meg"
},
"bulletin": {
"hide_grades": "Értékelések elrejtése",
"hide_grades_desc": "Az értékeléseid kártya elrejtése",
"hide_absences": "Mulasztások elrejtése",
"hide_absences_desc": "A mulasztások kártya elrejtése",
"hide_notes": "Feljegyzések elrejtése",
"hide_notes_desc": "A feljegyzések kártya elrejtése",
"hide_exams": "Bejelentett dolgozatok elrejtése",
"hide_exams_desc": "A bejelentett dolgozatok kártya elrejtése"
},
"grades": {
"hide_chart": "Grafikon elrejtése",
"hide_chart_desc": "A jegyek grafikonjának elrejtése",
"hide_class_average": "Osztályátlag elrejtése",
"hide_class_average_desc": "Az osztályátlag értékek elrejtése"
}
},
"themes": {
"light_blue": "Világos Kék",
"light_green": "Világos Zöld",
@@ -24,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",
@@ -36,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",
@@ -57,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": {
@@ -217,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",
@@ -228,10 +280,23 @@
"school": "Iskola",
"student_id": "Diák azonosító",
"settings_title": "Profil beállítások",
"tab_my_data": "Adataim",
"tab_settings": "Beállítások",
"tab_password": "Jelszó módosítása",
"tab_security": "Biztonsági beállítások",
"tab_contacts": "Elérhetőségek",
"loading_data": "Adatok betöltése...",
"loading_error": "Hiba történt az adatok betöltése során.",
"my_data_title": "Személyes adataim",
"personal_data": "Személyes adatok",
"contact_data": "Elérhetőségi adatok",
"address_data": "Lakcím adatok",
"bank_account_data": "Bankszámla adatok",
"student_card_data": "Tanulói igazolvány adatok",
"default_address": "Alapértelmezett",
"password_change_unavailable": "A jelszó módosítás funkció jelenleg nem érhető el.",
"security_settings_info": "A biztonsági beállítások kezeléséhez kattints az alábbi gombra:",
"open_security_settings": "Biztonsági beállítások megnyitása",
"two_factor_description": "A kétfaktoros hitelesítés használatához telepítsen egy időalapú, egyszer használatos jelszó (TOTP) alkalmazást:",
"android": "Android",
"iphone": "iPhone",
@@ -265,7 +330,31 @@
"passwords_not_match": "Az új jelszavak nem egyeznek!",
"password_too_short": "Az új jelszónak legalább 8 karakter hosszúnak kell lennie!",
"password_changed": "Jelszó sikeresen módosítva!",
"password_change_error": "Hiba történt a jelszó módosítása során. Kérjük, próbálja újra később."
"password_change_error": "Hiba történt a jelszó módosítása során. Kérjük, próbálja újra később.",
"field_familyName": "Családi név",
"field_firstName": "Utónév",
"field_birthFamilyName": "Születési családi név",
"field_birthFirstName": "Születési utónév",
"field_motherFamilyName": "Anyja születési családi neve",
"field_motherFirstName": "Anyja születési utóneve",
"field_birthDate": "Születési idő",
"field_birthPlace": "Születési hely",
"field_birthCountry": "Születési ország",
"field_motherTongue": "Anyanyelv",
"field_citizenship": "Állampolgárság",
"field_classTeacher": "Osztályfőnök",
"field_className": "Osztály",
"field_classroom": "Terem",
"field_username": "Felhasználónév",
"field_phone": "Telefonszám",
"field_email": "Értesítési e-mail cím",
"field_accountNumber": "Bankszámlaszám",
"field_bankName": "Számlavezető bank",
"field_accountOwner": "Bankszámla tulajdonos neve",
"field_cardNumber": "Igazolvány száma",
"field_taxId": "Adóazonosító jel",
"field_studentCardNumber": "Diákigazolvány szám",
"field_socialSecurityNumber": "TAJ-szám"
},
"login": {
"title": "Bejelentkezés",
@@ -311,6 +400,35 @@
"success": "Sikeres kijelentkezés!",
"continue": "Tovább"
},
"setup": {
"welcome": "Állítsd be a bővítményt néhány egyszerű lépésben",
"steps": {
"theme": "Téma",
"language": "Nyelv",
"finish": "Kész"
},
"theme": {
"title": "Válassz témát",
"description": "Válaszd ki a számodra legmegfelelőbb megjelenést"
},
"language": {
"title": "Válassz nyelvet",
"description": "Válaszd ki a használni kívánt nyelvet"
},
"finish": {
"title": "Minden kész!",
"description": "A beállítások sikeresen mentve. Indulhat a tanulás!",
"about": "Rólunk",
"about_desc": "Tudj meg többet a projektről",
"support": "Támogatás",
"support_desc": "Támogasd a fejlesztést",
"github": "GitHub",
"github_desc": "Nézd meg a forráskódot",
"discord": "Discord",
"discord_desc": "Csatlakozz a közösséghez",
"start": "Kezdés"
}
},
"common": {
"save": "Mentés",
"cancel": "Mégse",
@@ -342,8 +460,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

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" fill="none"><g fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="8" r="5" fill="currentColor"/><path d="M20 21a8 8 0 1 0-16 0"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="3 2 22 24" fill="none"><g fill-rule="evenodd" clip-rule="evenodd" fill="currentColor" stroke="currentColor" stroke-width="1.66667" stroke-linecap="round" stroke-linejoin="round"><circle cx="14" cy="9" r="5" fill="currentColor"/><path d="M22 22a8 8 0 1 0-16 0"/></g></svg>

Before

Width:  |  Height:  |  Size: 343 B

After

Width:  |  Height:  |  Size: 343 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

View File

@@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" fill="none"><path fill="currentColor" fill-rule="evenodd" d="M9.024 2.783A1 1 0 0 1 10 2h4a1 1 0 0 1 .976.783l.44 1.981q.6.285 1.14.66l1.938-.61a1 1 0 0 1 1.166.454l2 3.464a1 1 0 0 1-.19 1.237l-1.497 1.373a8 8 0 0 1 0 1.316l1.497 1.373a1 1 0 0 1 .19 1.237l-2 3.464a1 1 0 0 1-1.166.454l-1.937-.61q-.54.375-1.14.66l-.44 1.98A1 1 0 0 1 14 22h-4a1 1 0 0 1-.976-.783l-.44-1.981q-.6-.285-1.14-.66l-1.938.61a1 1 0 0 1-1.166-.454l-2-3.464a1 1 0 0 1 .19-1.237l1.497-1.373a8 8 0 0 1 0-1.316L2.53 9.97a1 1 0 0 1-.19-1.237l2-3.464a1 1 0 0 1 1.166-.454l1.937.61q.54-.375 1.14-.66l.44-1.98zM12 15a3 3 0 1 0 0-6a3 3 0 0 0 0 6" clip-rule="evenodd"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="2 2 24 24" fill="none">
<path fill="currentColor" fill-rule="evenodd" d="M 10.748 4.096 C 10.85 3.638 11.255 3.313 11.724 3.313 L 15.724 3.313 C 16.193 3.313 16.598 3.638 16.7 4.096 L 17.14 6.077 C 17.54 6.267 17.92 6.487 18.28 6.737 L 20.218 6.127 C 20.665 5.986 21.15 6.175 21.384 6.581 L 23.384 10.045 C 23.618 10.451 23.539 10.965 23.194 11.282 L 21.697 12.655 C 21.733 13.093 21.733 13.533 21.697 13.971 L 23.194 15.344 C 23.539 15.661 23.618 16.175 23.384 16.581 L 21.384 20.045 C 21.15 20.451 20.665 20.639 20.218 20.499 L 18.281 19.889 C 17.921 20.139 17.541 20.359 17.141 20.549 L 16.701 22.529 C 16.6 22.987 16.193 23.313 15.724 23.313 L 11.724 23.313 C 11.255 23.313 10.85 22.987 10.748 22.53 L 10.308 20.549 C 9.908 20.359 9.528 20.139 9.168 19.889 L 7.23 20.499 C 6.783 20.639 6.298 20.451 6.064 20.045 L 4.064 16.581 C 3.83 16.175 3.908 15.661 4.254 15.344 L 5.751 13.971 C 5.715 13.533 5.715 13.093 5.751 12.655 L 4.254 11.283 C 3.908 10.966 3.83 10.452 4.064 10.046 L 6.064 6.582 C 6.298 6.176 6.783 5.987 7.23 6.128 L 9.167 6.738 C 9.527 6.488 9.907 6.268 10.307 6.078 L 10.747 4.098 L 10.748 4.096 Z M 13.931 17.38 C 17.01 17.38 18.935 14.046 17.395 11.38 C 16.68 10.142 15.36 9.38 13.931 9.38 C 10.852 9.38 8.927 12.713 10.467 15.38 C 11.181 16.617 12.502 17.38 13.931 17.38" clip-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 722 B

After

Width:  |  Height:  |  Size: 1.4 KiB

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

BIN
images/folio.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -54,7 +54,6 @@ body {
flex-direction:column;
align-items:center;
gap:8px;
margin:16px 0;
background:var(--card-card) !important;
border-bottom:1px solid rgba(0,0,0,0) !important;
}
@@ -66,6 +65,7 @@ body {
font-style:normal;
font-weight:700;
line-height:normal;
margin-bottom: 12px;
}
.logo {
width:48px;

View File

@@ -1,11 +1,21 @@
async function transformLoginPage() {
try {
while (
typeof window.LanguageManager === "undefined" ||
!window.LanguageManager.t("login.username_placeholder") ||
window.LanguageManager.t("login.username_placeholder") === "login.username_placeholder"
) {
await new Promise((resolve) => setTimeout(resolve, 50));
}
if (document.readyState !== "complete") {
await new Promise((resolve) => {
window.addEventListener("load", resolve);
});
}
const loginSettings = await loadLoginSettings();
const existingForm = document.querySelector("form");
const formData = {
action: existingForm?.getAttribute("action") || "",
@@ -50,11 +60,11 @@ async function transformLoginPage() {
<img src=${chrome.runtime.getURL("images/firka_logo.png")} alt="Firka" class="logo">
Firka
</p>
<h1 class="school-name">${schoolInfo.name}</h1>
${!loginSettings.hideSchoolInfo ? `<h1 class="school-name">${schoolInfo.name}</h1>
<div class="school-details">
${schoolInfo.kretaId ? `<div>${schoolInfo.kretaId}</div>` : ""}
${schoolInfo.omCode ? `<div>${LanguageManager.t("login.kreta_id")}: ${schoolInfo.omCode}</div>` : ""}
</div>
</div>` : ''}
</div>
<form class="login-form" method="post" action="${formData.action}" id="loginForm" novalidate>
@@ -93,7 +103,7 @@ async function transformLoginPage() {
</div>
${
systemMessage
systemMessage && !loginSettings.hideSystemMessage
? `
<div class="system-message">
<h4>${LanguageManager.t("login.system_message")}</h4>
@@ -110,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) {
@@ -195,7 +203,35 @@ function handleSubmit(event) {
form.submit();
}
if (window.location.href.includes("idp.e-kreta.hu/Account/Login")) {
transformLoginPage().catch((error) => {
});
async function loadLoginSettings() {
try {
const settings = await storageManager.get("pageSettings_login", {});
return {
hideSystemMessage: settings.hideSystemMessage || false,
hideSchoolInfo: settings.hideSchoolInfo || false
};
} catch (error) {
console.error("Error loading login settings:", error);
return {
hideSystemMessage: false,
hideSchoolInfo: false
};
}
}
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === "pageSettingChanged" && message.pageType === "login") {
transformLoginPage();
}
});
if (window.location.href.includes("idp.e-kreta.hu/Account/Login")) {
(async () => {
while (typeof window.LanguageManager === "undefined") {
await new Promise((resolve) => setTimeout(resolve, 50));
}
transformLoginPage().catch((error) => {
console.error("Error transforming login page:", 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.2",
"version": "1.4.8",
"description": "KRÉTA webes verziójának újraírása",
"icons": {
"128": "images/firka_logo_128.png"
@@ -13,7 +13,8 @@
}
},
"permissions": [
"storage"
"storage",
"tabs"
],
"background": {
"service_worker": "tools/background.js"
@@ -31,6 +32,7 @@
{
"resources": [
"settings/*",
"setup/*",
"global/language.js",
"images/*",
"fonts/*.woff2",
@@ -189,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.2",
"version": "1.4.8",
"description": "KRÉTA webes verziójának újraírása",
"icons": {
"128": "images/firka_logo_128.png"
@@ -13,19 +13,18 @@
}
},
"permissions": [
"storage"
"storage",
"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": []
}
}
},
@@ -33,6 +32,7 @@
{
"resources": [
"settings/*",
"setup/*",
"global/language.js",
"images/*",
"fonts/*.woff2",
@@ -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

@@ -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

@@ -1,6 +1,5 @@
@import url('../global/theme.css');
.main-header,
.main-menu,
.main-sidebar,
@@ -11,11 +10,17 @@
.navbar,
.sidebar-container,
#sidepanel_tabs,
.sidepanel-wrapper {
.sidepanel-wrapper,
.main-footer2,
.lakatimg,
.modalContainer,
.modalOuter,
#ProfilTab,
#KretaProgressBar {
display: none !important;
visibility: hidden !important;
}
body {
margin: 0;
padding: 0;
@@ -26,6 +31,11 @@ body {
font-size: 16px;
}
h2 {
background-color: var(--card-card) !important;
border-bottom: none !important;
}
.page-wrapper {
background-color: var(--background) !important;
min-height: 100vh;
@@ -50,271 +60,295 @@ body {
padding: 0;
}
.content-container {
max-width: 1200px;
.profile-container {
max-width: 1400px;
margin: 0 auto;
padding: clamp(1rem, 3vw, 2rem);
background-color: var(--background) !important;
padding: 2rem;
}
.profile-wrapper {
display: grid;
grid-template-columns: 280px 1fr;
gap: 2rem;
min-height: calc(100vh - 200px);
}
.firka-header {
padding: clamp(1rem, 3vw, 2rem);
.profile-sidebar {
position: sticky;
top: 2rem;
height: fit-content;
}
.profile-tabs {
background: var(--card-card);
border-radius: 16px;
padding: 1rem;
box-shadow: 0px 1px 2px 0px var(--accent-shadow);
}
.profile-tab {
width: 100%;
display: flex;
align-items: center;
gap: 1rem;
background-color: var(--background);
gap: 0.75rem;
padding: 1rem;
border: none;
background: transparent;
color: var(--text-secondary);
font-family: 'Montserrat', sans-serif;
font-size: 14px;
font-weight: 500;
cursor: pointer;
border-radius: 12px;
transition: all 0.2s ease;
text-align: left;
}
.profile-tab:hover {
background: var(--button-secondaryFill);
color: var(--text-primary);
}
.profile-tab.active {
background: var(--accent-accent);
color: white;
}
.profile-tab svg {
flex-shrink: 0;
}
.profile-content {
background: var(--card-card);
border-radius: 16px;
padding: 2rem;
box-shadow: 0px 1px 2px 0px var(--accent-shadow);
min-height: 500px;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.tab-header {
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--accent-15);
}
.tab-header h2 {
margin: 0;
color: var(--text-primary);
font-size: 28px;
font-weight: 600;
}
.data-section {
margin-bottom: 2.5rem;
}
.data-section:last-child {
margin-bottom: 0;
}
.data-section h3 {
color: var(--text-primary);
font-size: 20px;
font-weight: 600;
margin: 0 0 1.5rem 0;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--accent-15);
}
.back-button {
.data-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.data-item {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.data-item label {
color: var(--text-secondary);
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.data-item span {
color: var(--text-primary);
font-size: 15px;
font-weight: 500;
padding: 0.75rem;
background: var(--button-secondaryFill);
border-radius: 8px;
word-break: break-word;
}
.address-list {
display: flex;
flex-direction: column;
gap: 1rem;
}
.address-item {
padding: 1.25rem;
background: var(--button-secondaryFill);
border-radius: 12px;
border-left: 4px solid var(--accent-15);
transition: all 0.2s ease;
}
.address-item:hover {
transform: translateX(4px);
box-shadow: 0px 2px 8px 0px var(--accent-shadow);
}
.address-item.default {
border-left-color: var(--accent-accent);
background: var(--accent-15);
}
.address-type {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--card-card);
border: none;
border-radius: 12px;
padding: 12px 16px;
gap: 0.75rem;
margin-bottom: 0.5rem;
}
.address-type strong {
color: var(--text-primary);
font-family: 'Montserrat', sans-serif;
font-weight: 500;
font-size: 14px;
cursor: pointer;
transition: all 0.2s ease;
text-decoration: none;
box-shadow: 0px 1px var(--shadow-blur, 2px) 0px var(--accent-shadow);
}
.back-button:hover {
background: var(--button-secondaryFill);
transform: translateY(-1px);
box-shadow: 0px 2px var(--shadow-blur, 4px) 0px var(--accent-shadow);
}
.back-button svg {
width: 16px;
height: 16px;
fill: var(--text-primary);
}
.page-title {
color: var(--text-primary);
font-size: 24px;
font-size: 15px;
font-weight: 600;
}
.badge {
display: inline-block;
padding: 0.25rem 0.75rem;
background: var(--accent-accent);
color: white;
font-size: 11px;
font-weight: 600;
border-radius: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.address-details {
color: var(--text-secondary);
font-size: 14px;
line-height: 1.5;
}
.loading-spinner {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
gap: 1.5rem;
}
.spinner {
width: 48px;
height: 48px;
border: 4px solid var(--accent-15);
border-top-color: var(--accent-accent);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-spinner p {
color: var(--text-secondary);
font-size: 16px;
margin: 0;
}
.k-content {
background-color: var(--background) !important;
}
.k-content h4 {
color: var(--text-primary) !important;
font-family: 'Montserrat', sans-serif !important;
font-size: 28px !important;
font-weight: 600 !important;
margin: 0 0 2rem 0 !important;
.info-message {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
padding: 3rem 2rem;
text-align: center;
}
.k-tabstrip-wrapper {
background: var(--card-card) !important;
border-radius: 24px !important;
overflow: hidden;
box-shadow: 0px 1px var(--shadow-blur, 2px) 0px var(--accent-shadow);
border: none !important;
}
.k-tabstrip {
background: var(--card-card) !important;
border: none !important;
}
.k-tabstrip-items {
background: var(--card-card) !important;
border: none !important;
border-radius: 24px 24px 0 0 !important;
padding: 0 20px !important;
}
.k-tabstrip-items .k-item {
background: transparent !important;
border: none !important;
margin: 0 !important;
border-radius: 0 !important;
}
.k-tabstrip-items .k-item .k-link {
color: var(--text-secondary) !important;
font-family: 'Montserrat', sans-serif !important;
font-weight: 500 !important;
font-size: 14px !important;
padding: 16px 20px !important;
border: none !important;
background: transparent !important;
border-radius: 0 !important;
transition: all 0.2s ease !important;
}
.k-tabstrip-items .k-item.k-state-active .k-link {
color: var(--accent-accent) !important;
font-weight: 600 !important;
border-bottom: 2px solid var(--accent-accent) !important;
}
.k-tabstrip-items .k-item:hover .k-link {
color: var(--text-primary) !important;
}
.k-tabstrip .k-content {
background: var(--card-card) !important;
border: none !important;
padding: 20px !important;
border-radius: 0 0 24px 24px !important;
}
form {
background: transparent !important;
}
.container-fluid.details {
background: transparent !important;
margin-bottom: 2rem;
}
.row {
margin-bottom: 1rem;
align-items: center;
}
.windowInputLabel {
color: var(--text-primary) !important;
font-family: 'Montserrat', sans-serif !important;
font-weight: 500 !important;
font-size: 14px !important;
margin: 0 !important;
}
input[type="text"],
input[type="password"],
input[type="email"],
select,
textarea {
background: var(--button-secondaryFill) !important;
border: 1px solid var(--accent-15) !important;
border-radius: 12px !important;
padding: 12px 16px !important;
color: var(--text-primary) !important;
font-family: 'Montserrat', sans-serif !important;
font-size: 14px !important;
transition: all 0.2s ease !important;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
select:focus,
textarea:focus {
outline: none !important;
border-color: var(--accent-accent) !important;
box-shadow: 0 0 0 3px var(--accent-15) !important;
}
.k-checkbox {
appearance: none;
width: 20px !important;
height: 20px !important;
border: 2px solid var(--accent-15) !important;
border-radius: 4px !important;
background: var(--button-secondaryFill) !important;
cursor: pointer !important;
position: relative !important;
transition: all 0.2s ease !important;
}
.k-checkbox:checked {
background: var(--accent-accent) !important;
border-color: var(--accent-accent) !important;
}
.k-checkbox:checked::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
.info-message svg {
color: white;
font-size: 12px;
font-weight: bold;
}
.k-checkbox-label {
margin-left: 8px !important;
color: var(--text-primary) !important;
font-family: 'Montserrat', sans-serif !important;
cursor: pointer !important;
.info-message p {
color: var(--text-secondary);
font-size: 16px;
line-height: 1.6;
margin: 0;
max-width: 600px;
}
.k-button,
button {
background: var(--accent-accent) !important;
border: none !important;
border-radius: 12px !important;
padding: 12px 24px !important;
color: white !important;
font-family: 'Montserrat', sans-serif !important;
font-weight: 600 !important;
font-size: 14px !important;
cursor: pointer !important;
transition: all 0.2s ease !important;
box-shadow: 0px 1px var(--shadow-blur, 2px) 0px var(--accent-shadow) !important;
.error-message {
display: flex;
flex-direction: column;
align-items: center;
gap: 1.5rem;
padding: 4rem 2rem;
text-align: center;
}
.k-button:hover,
button:hover {
background: var(--accent-secondary) !important;
transform: translateY(-1px) !important;
box-shadow: 0px 2px var(--shadow-blur, 4px) 0px var(--accent-shadow) !important;
.error-message svg {
color: #e74c3c;
}
.k-button:active,
button:active {
transform: translateY(0) !important;
.error-message p {
color: var(--text-secondary);
font-size: 16px;
margin: 0;
}
@media (max-width: 768px) {
.content-container {
padding: 1rem;
}
.firka-header {
padding: 1rem;
}
.page-title {
font-size: 20px;
}
.k-content h4 {
font-size: 24px !important;
}
.k-tabstrip-items {
padding: 0 10px !important;
}
.k-tabstrip-items .k-item .k-link {
padding: 12px 16px !important;
font-size: 13px !important;
}
.security-btn {
display: inline-flex;
align-items: center;
gap: 0.75rem;
padding: 1rem 2rem;
background: var(--accent-accent);
color: white;
border: none;
border-radius: 12px;
font-family: 'Montserrat', sans-serif;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0px 2px 4px 0px var(--accent-shadow);
}
.security-btn:hover {
background: var(--accent-secondary);
transform: translateY(-2px);
box-shadow: 0px 4px 8px 0px var(--accent-shadow);
}
.security-btn:active {
transform: translateY(0);
}
.security-btn svg {
flex-shrink: 0;
}
@keyframes fadeIn {
from {
@@ -327,159 +361,267 @@ button:active {
}
}
.k-tabstrip-wrapper {
.profile-wrapper {
animation: fadeIn 0.4s ease forwards;
}
.data-section {
animation: fadeIn 0.5s ease forwards;
}
.k-widget,
.k-header {
background: var(--card-card) !important;
color: var(--text-primary) !important;
border: none !important;
.data-section:nth-child(2) {
animation-delay: 0.1s;
}
.k-state-default {
background: transparent !important;
border: none !important;
.data-section:nth-child(3) {
animation-delay: 0.2s;
}
.k-state-active {
background: transparent !important;
.data-section:nth-child(4) {
animation-delay: 0.3s;
}
@media (max-width: 1024px) {
.profile-wrapper {
grid-template-columns: 1fr;
gap: 1.5rem;
}
.k-overlay,
.k-window,
.k-notification {
display: none !important;
.profile-sidebar {
position: static;
}
.profile-tabs {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
padding: 0.5rem;
}
.profile-tab {
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
gap: 0.5rem;
padding: 1rem 0.5rem;
font-size: 13px;
min-height: 75px;
}
.profile-tab svg {
width: 22px;
height: 22px;
}
.profile-tab span {
line-height: 1.2;
word-break: break-word;
hyphens: auto;
}
.data-grid {
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
}
@media (max-width: 768px) {
.profile-container {
padding: 0.75rem;
}
.main-content .content-content {
display: block !important;
.profile-wrapper {
gap: 0.75rem;
}
.profile-content {
padding: 1rem;
}
.data-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
.tab-header h2 {
font-size: 22px;
}
.data-section h3 {
font-size: 17px;
}
.profile-tabs {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.375rem;
padding: 0.375rem;
}
.profile-tab {
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;
gap: 0.375rem;
padding: 0.625rem 0.25rem;
font-size: 11px;
min-height: 55px;
}
.profile-tab svg {
width: 18px;
height: 18px;
}
.profile-tab span {
line-height: 1.1;
word-break: break-word;
hyphens: auto;
}
.address-item {
padding: 1rem;
}
.security-btn {
width: 100%;
justify-content: center;
}
.info-message,
.error-message,
.loading-spinner {
padding: 2rem 1rem;
}
}
@media (max-width: 480px) {
.profile-container {
padding: 0.5rem;
}
div[style*="display:flex;justify-content:space-between"] {
display: flex !important;
justify-content: space-between !important;
align-items: center !important;
margin-top: 2rem !important;
padding-top: 2rem !important;
border-top: 1px solid var(--accent-15) !important;
.profile-wrapper {
gap: 0.5rem;
}
.profile-content {
padding: 0.875rem;
border-radius: 12px;
}
.tab-header {
margin-bottom: 1.25rem;
}
.tab-header h2 {
font-size: 18px;
}
.data-section h3 {
font-size: 15px;
margin-bottom: 1rem;
}
.data-item label {
font-size: 11px;
}
.data-item span {
font-size: 13px;
padding: 0.5rem;
}
.profile-tabs {
padding: 0.25rem;
gap: 0.25rem;
border-radius: 10px;
}
.profile-tab {
padding: 0.5rem 0.25rem;
min-height: 50px;
font-size: 10px;
border-radius: 8px;
}
.profile-tab svg {
width: 16px;
height: 16px;
}
.profile-tab span {
font-size: 10px;
}
.data-section {
margin-bottom: 1.75rem;
}
.address-item {
padding: 0.75rem;
}
.badge {
font-size: 9px;
padding: 0.2rem 0.5rem;
}
}
div[style*="display:flex;justify-content:space-between"] label {
color: var(--text-secondary) !important;
font-size: 12px !important;
font-style: italic !important;
margin: 0 !important;
@media (prefers-color-scheme: dark) {
.error-message svg {
color: #ff6b6b;
}
}
.hidden-contact-info {
display: none !important;
visibility: hidden !important;
@media print {
.profile-sidebar,
.security-btn,
.kreta-header,
.mobile-header,
.mobile-bottom-nav {
display: none !important;
}
.profile-wrapper {
grid-template-columns: 1fr;
}
.profile-content {
box-shadow: none;
border: 1px solid #ddd;
}
.tab-content {
display: block !important;
}
.data-section {
page-break-inside: avoid;
}
}
.hidden-tab {
display: none !important;
.profile-tab:focus,
.security-btn:focus {
outline: 2px solid var(--accent-accent);
outline-offset: 2px;
}
#ProfilTab-3 {
padding: 20px;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 500px;
.profile-tab:focus:not(:focus-visible),
.security-btn:focus:not(:focus-visible) {
outline: none;
}
#ProfilTab-3 .container-fluid {
background: white;
border-radius: 12px;
padding: 30px;
margin-top: 20px;
html {
scroll-behavior: smooth;
}
#ProfilTab-3 h4 {
color: #2c3e50;
font-weight: 600;
margin-bottom: 25px;
padding-bottom: 10px;
border-bottom: 3px solid #3498db;
display: inline-block;
}
#ProfilTab-3 .row {
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #3498db;
transition: all 0.3s ease;
}
#ProfilTab-3 .row:hover {
background: #e3f2fd;
transform: translateX(5px);
box-shadow: 0 2px 10px rgba(52, 152, 219, 0.2);
}
#ProfilTab-3 .windowInputLabel {
color: #34495e;
font-weight: 500;
font-size: 14px;
}
#ProfilTab-3 .k-button {
background: linear-gradient(135deg, #3498db, #2980b9);
border: none;
border-radius: 6px;
padding: 10px 20px;
::selection {
background: var(--accent-accent);
color: white;
font-weight: 500;
transition: all 0.3s ease;
}
#ProfilTab-3 .k-button:hover {
background: linear-gradient(135deg, #2980b9, #1f5f8b);
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(52, 152, 219, 0.4);
::-moz-selection {
background: var(--accent-accent);
color: white;
}
#ProfilTab-3 .k-input {
border: 2px solid #e0e6ed;
border-radius: 6px;
padding: 10px;
transition: border-color 0.3s ease;
}
#ProfilTab-3 .k-input:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.1);
}
#ProfilTab-3 .alert {
border-radius: 8px;
border: none;
padding: 15px;
margin: 15px 0;
}
#ProfilTab-3 .alert-info {
background: linear-gradient(135deg, #e3f2fd, #bbdefb);
color: #1565c0;
}
#ProfilTab-3 .alert-success {
background: linear-gradient(135deg, #e8f5e8, #c8e6c9);
color: #2e7d32;
}
#ProfilTab-3 .alert-warning {
background: linear-gradient(135deg, #fff3e0, #ffcc02);
color: #ef6c00;
}
.details .row {
border-bottom: none !important;
}

View File

@@ -1,228 +1,497 @@
(function() {
'use strict';
function hideLoadingScreen() {
const loadingElement = document.getElementById('KretaProgressBar');
if (loadingElement) {
loadingElement.style.display = 'none !important';
loadingElement.style.visibility = 'hidden';
loadingElement.style.opacity = '0';
loadingElement.remove();
}
const TABS = {
MY_DATA: 'my-data',
PASSWORD: 'password',
SECURITY: 'security'
};
const loadingElements = document.querySelectorAll('[class*="loading"], [id*="loading"], [class*="Loading"], [id*="Loading"]');
loadingElements.forEach(el => {
el.style.display = 'none !important';
el.style.visibility = 'hidden';
el.style.opacity = '0';
});
let currentTab = TABS.MY_DATA;
let profileData = null;
let addressData = null;
function getApiUrls(subdomain) {
return {
PROFILE_DATA: `https://${subdomain}.e-kreta.hu/Adminisztracio/Profil/SajatAdatlapPopUp`,
ADDRESS_DATA: `https://${subdomain}.e-kreta.hu/api/ProfilApi/GetElerhetosegCimGrid?sort=&page=1&pageSize=100&group=&filter=&data=%7B%7D&_=`
};
}
function addBackButton() {
function hideOriginalElements() {
const elementsToHide = [
'#KretaProgressBar',
'.main-header',
'.main-menu',
'.main-sidebar',
'.content-header',
'.favoriteIconContainer',
'#frissitesDatumDiv',
'#layout_navigationBar',
'.navbar',
'.sidebar-container',
'#sidepanel_tabs',
'.sidepanel-wrapper',
'.main-footer2',
'.lakatimg',
'.modalContainer',
'.modalOuter',
'#ProfilTab'
];
if (document.getElementById('firka-back-button')) {
return;
}
const backButton = document.createElement('button');
backButton.id = 'firka-back-button';
backButton.innerHTML = '← Vissza';
backButton.style.cssText = `
position: static;
margin: 20px;
z-index: 100;
background-color: var(--card-background, #ffffff);
color: var(--text-primary, #333333);
border: 1px solid var(--border-color, #e0e0e0);
border-radius: 8px;
padding: 10px 16px;
font-family: 'Montserrat', sans-serif;
font-size: 14px;
font-weight: 500;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
display: inline-flex;
align-items: center;
gap: 8px;
width: auto;
`;
backButton.addEventListener('mouseenter', function() {
this.style.backgroundColor = 'var(--card-hover, #f5f5f5)';
this.style.transform = 'translateY(-1px)';
this.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.15)';
elementsToHide.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(el => {
el.style.display = 'none';
el.style.visibility = 'hidden';
});
});
backButton.addEventListener('mouseleave', function() {
this.style.backgroundColor = 'var(--card-background, #ffffff)';
this.style.transform = 'translateY(0)';
this.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.1)';
document.body.innerHTML = '';
}
async function createNavbar() {
const header = await createTemplate.header();
const headerContainer = document.createElement('div');
headerContainer.innerHTML = header;
document.body.appendChild(headerContainer);
setupUserDropdown();
setupSettingsButton();
}
function setupUserDropdown() {
const userBtn = document.querySelector('.user-dropdown-btn');
const userDropdown = document.querySelector('.user-dropdown');
userBtn?.addEventListener('click', (e) => {
e.stopPropagation();
userDropdown?.classList.toggle('show');
userBtn?.classList.toggle('open');
});
backButton.addEventListener('click', function() {
window.history.back();
const mobileUserBtn = document.querySelector('#mobileUserBtn');
const mobileUserDropdown = document.querySelector('#mobileUserDropdown');
mobileUserBtn?.addEventListener('click', (e) => {
e.stopPropagation();
mobileUserDropdown?.classList.toggle('show');
mobileUserBtn?.classList.toggle('active');
});
document.body.insertBefore(backButton, document.body.firstChild);
document.addEventListener('click', (e) => {
if (!userBtn?.contains(e.target) && !userDropdown?.contains(e.target)) {
userDropdown?.classList.remove('show');
userBtn?.classList.remove('open');
}
}
function hideMainFooter2() {
const footer2 = document.querySelector('.main-footer2');
if (footer2) {
footer2.style.display = 'none';
}
}
function hideLakatImg() {
const lakatImg = document.querySelector('.lakatimg');
if (lakatImg) {
lakatImg.style.display = 'none';
}
}
function hideCustomUserSettingsTab() {
const firstTab = document.querySelector('#ProfilTab .k-tabstrip-items li[aria-controls="ProfilTab-1"]');
if (firstTab) {
firstTab.classList.add('hidden-tab');
}
const tabLinks = document.querySelectorAll('#ProfilTab .k-tabstrip-items .k-link');
tabLinks.forEach(link => {
if (link.textContent && link.textContent.includes('Egyedi felhasználó beállítások')) {
const parentTab = link.closest('li');
if (parentTab) {
parentTab.classList.add('hidden-tab');
}
if (!mobileUserBtn?.contains(e.target) && !mobileUserDropdown?.contains(e.target)) {
mobileUserDropdown?.classList.remove('show');
mobileUserBtn?.classList.remove('active');
}
});
const contentPanel = document.querySelector('#ProfilTab-1');
if (contentPanel) {
contentPanel.classList.add('hidden-tab');
const mobileDropdownItems = document.querySelectorAll('.mobile-dropdown-item');
mobileDropdownItems.forEach(item => {
item.addEventListener('click', () => {
mobileUserDropdown?.classList.remove('show');
mobileUserBtn?.classList.remove('active');
});
});
}
function setupSettingsButton() {
document.getElementById('settingsBtn')?.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const url = chrome.runtime.getURL('settings/index.html');
window.open(url, '_blank', 'width=400,height=600');
});
document.getElementById('mobileSettingsBtn')?.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
const url = chrome.runtime.getURL('settings/index.html');
window.open(url, '_blank', 'width=400,height=600');
});
}
function createProfilePage() {
const t = window.LanguageManager?.t || ((key) => key);
const container = document.createElement('div');
container.className = 'profile-container';
container.innerHTML = `
<div class="profile-wrapper">
<div class="profile-sidebar">
<div class="profile-tabs">
<button class="profile-tab active" data-tab="${TABS.MY_DATA}">
<svg viewBox="0 0 24 24" width="20" height="20">
<path fill="currentColor" d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>
</svg>
<span>${t('profile.tab_my_data')}</span>
</button>
<button class="profile-tab" data-tab="${TABS.PASSWORD}">
<svg viewBox="0 0 24 24" width="20" height="20">
<path fill="currentColor" d="M18 8h-1V6c0-2.76-2.24-5-5-5S7 3.24 7 6v2H6c-1.1 0-2 .9-2 2v10c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V10c0-1.1-.9-2-2-2zm-6 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm3.1-9H8.9V6c0-1.71 1.39-3.1 3.1-3.1 1.71 0 3.1 1.39 3.1 3.1v2z"/>
</svg>
<span>${t('profile.tab_password')}</span>
</button>
<button class="profile-tab" data-tab="${TABS.SECURITY}">
<svg viewBox="0 0 24 24" width="20" height="20">
<path fill="currentColor" d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
</svg>
<span>${t('profile.tab_security')}</span>
</button>
</div>
</div>
<div class="profile-content">
<div id="tab-${TABS.MY_DATA}" class="tab-content active">
<div class="loading-spinner">
<div class="spinner"></div>
<p>${t('profile.loading_data')}</p>
</div>
</div>
<div id="tab-${TABS.PASSWORD}" class="tab-content">
<div class="tab-header">
<h2>${t('profile.tab_password')}</h2>
</div>
<div class="info-message">
<svg viewBox="0 0 24 24" width="24" height="24">
<path fill="currentColor" d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/>
</svg>
<p>${t('profile.password_change_unavailable')}</p>
</div>
</div>
<div id="tab-${TABS.SECURITY}" class="tab-content">
<div class="tab-header">
<h2>${t('profile.tab_security')}</h2>
</div>
<div class="info-message">
<p>${t('profile.security_settings_info')}</p>
<button id="security-redirect-btn" class="security-btn">
<svg viewBox="0 0 24 24" width="20" height="20">
<path fill="currentColor" d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm0 10.99h7c-.53 4.12-3.28 7.79-7 8.94V12H5V6.3l7-3.11v8.8z"/>
</svg>
${t('profile.open_security_settings')}
</button>
</div>
</div>
</div>
</div>
`;
document.body.appendChild(container);
}
async function fetchProfileData() {
try {
const subdomain = await storageManager.get('schoolSubdomain', '');
if (!subdomain) {
console.error('Nincs beállítva az iskola alcím');
return null;
}
const apiUrls = getApiUrls(subdomain);
const response = await fetch(apiUrls.PROFILE_DATA);
const html = await response.text();
return parseProfileHTML(html);
} catch (error) {
console.error('Hiba a profil adatok lekérésekor:', error);
return null;
}
}
function hideAdditionalContactInfo() {
async function fetchAddressData() {
try {
const subdomain = await storageManager.get('schoolSubdomain', '');
if (!subdomain) {
console.error('Nincs beállítva az iskola alcím');
return null;
}
function hideElementsWithText(text) {
const elements = document.querySelectorAll('h4');
elements.forEach(h4 => {
if (h4.textContent && h4.textContent.includes(text)) {
// Hide the parent row
let parent = h4.closest('.row');
if (parent) {
parent.classList.add('hidden-contact-info');
const apiUrls = getApiUrls(subdomain);
const timestamp = new Date().getTime();
const response = await fetch(apiUrls.ADDRESS_DATA + timestamp);
const data = await response.json();
return data;
} catch (error) {
console.error('Hiba a cím adatok lekérésekor:', error);
return null;
}
}
function parseProfileHTML(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
const data = {
personal: {},
contact: {},
bankAccount: {},
studentCard: {}
};
const personalFields = [
{ id: 'AlapAdat_CsaladiNev', key: 'familyName' },
{ id: 'AlapAdat_Utonev', key: 'firstName' },
{ id: 'AlapAdat_SzuletesiCsaladNev', key: 'birthFamilyName' },
{ id: 'AlapAdat_SzuletesiUtonev', key: 'birthFirstName' },
{ id: 'AlapAdat_AnyjaCsaladiNeve', key: 'motherFamilyName' },
{ id: 'AlapAdat_AnyjaUtonev', key: 'motherFirstName' },
{ id: 'AlapAdat_SzuletesiIdo_SDATE', key: 'birthDate' },
{ id: 'AlapAdat_SzuletesiHely', key: 'birthPlace' },
{ id: 'AlapAdat_SzuletesiOrszag', key: 'birthCountry' },
{ id: 'AlapAdat_Anyanyelv', key: 'motherTongue' },
{ id: 'AlapAdat_Allampolgarsag', key: 'citizenship' },
{ id: 'AlapAdat_OsztalyfonokNev', key: 'classTeacher' },
{ id: 'AlapAdat_OsztalyNev', key: 'className' },
{ id: 'AlapAdat_TeremNev', key: 'classroom' },
{ id: 'AlapAdat_BelepesiNev', key: 'username' }
];
personalFields.forEach(field => {
const label = doc.querySelector(`label[displayfor="${field.id}"]`);
if (label) {
data.personal[field.key] = {
key: field.key,
value: label.textContent.trim()
};
}
});
const phoneInput = doc.querySelector('#Elerhetosegek_ErtesitesiTelefon_TelefonSzam');
const emailInput = doc.querySelector('#Elerhetosegek_ErtesitesiEmail_EmailCim');
if (phoneInput) {
data.contact.phone = {
key: 'phone',
value: phoneInput.value || ''
};
}
if (emailInput) {
data.contact.email = {
key: 'email',
value: emailInput.value || ''
};
}
const bankFields = [
{ id: 'BankszamlaSzam', key: 'accountNumber' },
{ id: 'SzamlavezetoBank', key: 'bankName' },
{ id: 'BankszamlaTulajdonosNeve', key: 'accountOwner' }
];
bankFields.forEach(field => {
const input = doc.querySelector(`#${field.id}`);
if (input) {
data.bankAccount[field.key] = {
key: field.key,
value: input.value || ''
};
}
});
const cardFields = [
{ id: 'TanuloIgazolvany_Igazolvanyszam', key: 'cardNumber' },
{ id: 'TanuloIgazolvany_AdoazonositoJel', key: 'taxId' },
{ id: 'TanuloIgazolvany_DiakigazolvanySzam', key: 'studentCardNumber' },
{ id: 'TanuloIgazolvany_TajSzam', key: 'socialSecurityNumber' }
];
cardFields.forEach(field => {
const label = doc.querySelector(`label[displayfor="${field.id}"]`);
if (label) {
data.studentCard[field.key] = {
key: field.key,
value: label.textContent.trim()
};
}
});
return data;
}
function renderProfileData() {
const t = window.LanguageManager?.t || ((key) => key);
const container = document.getElementById(`tab-${TABS.MY_DATA}`);
if (!profileData && !addressData) {
container.innerHTML = `
<div class="error-message">
<svg viewBox="0 0 24 24" width="48" height="48">
<path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/>
</svg>
<p>${t('profile.loading_error')}</p>
</div>
`;
return;
}
let html = `<div class="tab-header"><h2>${t('profile.my_data_title')}</h2></div>`;
if (profileData && profileData.personal && Object.keys(profileData.personal).length > 0) {
html += `<div class="data-section"><h3>${t('profile.personal_data')}</h3><div class="data-grid">`;
Object.values(profileData.personal).forEach(field => {
if (field.value && field.value !== 'XY') {
html += `
<div class="data-item">
<label>${t(`profile.field_${field.key}`)}</label>
<span>${field.value}</span>
</div>
`;
}
});
html += '</div></div>';
}
if (profileData && profileData.contact && Object.keys(profileData.contact).length > 0) {
html += `<div class="data-section"><h3>${t('profile.contact_data')}</h3><div class="data-grid">`;
Object.values(profileData.contact).forEach(field => {
if (field.value) {
html += `
<div class="data-item">
<label>${t(`profile.field_${field.key}`)}</label>
<span>${field.value}</span>
</div>
`;
}
});
html += '</div></div>';
}
if (addressData && addressData.Data && addressData.Data.length > 0) {
html += `<div class="data-section"><h3>${t('profile.address_data')}</h3><div class="address-list">`;
addressData.Data.forEach(address => {
const fullAddress = `${address.Irsz} ${address.Varos}, ${address.Kozterulet} ${address.KozteruletJellegeNev || ''} ${address.HazSzam}${address.Emelet ? ' ' + address.Emelet : ''}${address.Ajto ? ' ' + address.Ajto : ''}`;
const isDefault = address.Alapertelmezett_BOOL;
html += `
<div class="address-item ${isDefault ? 'default' : ''}">
<div class="address-type">
<strong>${address.CimTipus_DNAME}</strong>
${isDefault ? `<span class="badge">${t('profile.default_address')}</span>` : ''}
</div>
<div class="address-details">${fullAddress}</div>
</div>
`;
});
html += '</div></div>';
}
if (profileData && profileData.bankAccount && Object.keys(profileData.bankAccount).length > 0) {
const hasData = Object.values(profileData.bankAccount).some(field => field.value);
if (hasData) {
html += `<div class="data-section"><h3>${t('profile.bank_account_data')}</h3><div class="data-grid">`;
Object.values(profileData.bankAccount).forEach(field => {
if (field.value) {
html += `
<div class="data-item">
<label>${t(`profile.field_${field.key}`)}</label>
<span>${field.value}</span>
</div>
`;
}
}
});
html += '</div></div>';
}
}
if (profileData && profileData.studentCard && Object.keys(profileData.studentCard).length > 0) {
const hasData = Object.values(profileData.studentCard).some(field => field.value && field.value !== 'XY');
if (hasData) {
html += `<div class="data-section"><h3>${t('profile.student_card_data')}</h3><div class="data-grid">`;
Object.values(profileData.studentCard).forEach(field => {
if (field.value && field.value !== 'XY') {
html += `
<div class="data-item">
<label>${t(`profile.field_${field.key}`)}</label>
<span>${field.value}</span>
</div>
`;
}
});
html += '</div></div>';
}
}
container.innerHTML = html;
}
function setupTabSwitching() {
const tabs = document.querySelectorAll('.profile-tab');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabId = tab.getAttribute('data-tab');
tabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
tabContents.forEach(tc => tc.classList.remove('active'));
document.getElementById(`tab-${tabId}`).classList.add('active');
currentTab = tabId;
});
});
}
async function setupSecurityRedirect() {
const btn = document.getElementById('security-redirect-btn');
if (btn) {
btn.addEventListener('click', async () => {
const subdomain = await storageManager.get('schoolSubdomain', '');
const url = `https://idp.e-kreta.hu/security?returnUrl=https%3A%2F%2F${subdomain}.e-kreta.hu%2FAdminisztracio%2FProfil`;
window.location.href = url;
});
}
hideElementsWithText('TOVÁBBI E-MAIL ELÉRHETŐSÉGEK');
hideElementsWithText('TOVÁBBI TELEFONSZÁMOK');
const rows = document.querySelectorAll('.row');
rows.forEach(row => {
const h4Elements = row.querySelectorAll('h4');
h4Elements.forEach(h4 => {
if (h4.textContent &&
(h4.textContent.includes('TOVÁBBI E-MAIL ELÉRHETŐSÉGEK') ||
h4.textContent.includes('TOVÁBBI TELEFONSZÁMOK'))) {
row.classList.add('hidden-contact-info');
}
});
});
}
function init() {
hideLoadingScreen();
addBackButton();
hideAdditionalContactInfo();
hideCustomUserSettingsTab();
hideMainFooter2();
hideLakatImg();
async function init() {
try {
hideOriginalElements();
const observer = new MutationObserver(() => {
hideAdditionalContactInfo();
hideCustomUserSettingsTab();
hideMainFooter2();
hideLakatImg();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
if (typeof window.LanguageManager !== 'undefined' && window.LanguageManager.init) {
await window.LanguageManager.init();
}
await createNavbar();
createProfilePage();
setupTabSwitching();
setupSecurityRedirect();
[profileData, addressData] = await Promise.all([
fetchProfileData(),
fetchAddressData()
]);
renderProfileData();
} catch (error) {
console.error('Hiba az inicializálás során:', error);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
window.addEventListener('load', init);
setTimeout(hideLoadingScreen, 1000);
let attempts = 0;
const maxAttempts = 20;
const aggressiveHide = setInterval(() => {
attempts++;
hideLoadingScreen();
if (attempts >= maxAttempts) {
clearInterval(aggressiveHide);
}
}, 500);
if (typeof MutationObserver !== 'undefined') {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1) {
if (node.id === 'KretaProgressBar' ||
node.className && (node.className.includes('loading') || node.className.includes('Loading'))) {
node.style.display = 'none !important';
node.style.visibility = 'hidden';
node.style.opacity = '0';
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
})();
})();

View File

@@ -18,6 +18,25 @@
return setInterval(updateTimer, 1000);
};
async function loadRoleselectSettings() {
try {
const settings = await storageManager.get("pageSettings_roleselect", {
autoRedirect: false,
hideSchoolInfo: true
});
return {
autoRedirect: settings.autoRedirect || false,
hideSchoolInfo: settings.hideSchoolInfo !== undefined ? settings.hideSchoolInfo : true
};
} catch (error) {
console.error("Error loading roleselect settings:", error);
return {
autoRedirect: false,
hideSchoolInfo: true
};
}
}
const handleRoleChange = async (role) => {
try {
const response = await fetch(
@@ -41,7 +60,7 @@
}
};
const createHTML = (schoolCode, fullSchoolName, userName) => `
const createHTML = (schoolCode, fullSchoolName, userName, settings) => `
<div class="kreta-container">
<header class="kreta-header">
<div class="school-info">
@@ -49,15 +68,17 @@
<img src=${chrome.runtime.getURL("images/firka_logo.png")} alt="Firka" class="logo">
Firka
</p>
<div class="school-details">
${!settings.hideSchoolInfo ? `<div class="school-details">
<span>${schoolCode || ""} - ${fullSchoolName || "Iskola"}</span>
</div>
</div>` : ''}
</div>
<div class="user-profile">
<div class="user-info">
${!settings.hideSchoolInfo ? `<div class="user-info">
<span class="user-name">${userName}</span>
<span class="logout-timer" id="logoutTimer">5:00</span>
</div>
</div>` : `<div class="user-info">
<span class="logout-timer" id="logoutTimer">5:00</span>
</div>`}
</div>
</header>
@@ -108,6 +129,12 @@
window.addEventListener("load", resolve),
);
}
const settings = await loadRoleselectSettings();
if (settings.autoRedirect) {
handleRoleChange("Ellenorzo");
return;
}
const schoolNameEl = document.querySelector(".IntezmenyNev");
const schoolName = schoolNameEl?.textContent.trim() || "Iskola neve";
@@ -127,14 +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,
), 'text/html');
const tempDiv = doc.body;
settings,
);
const tempDiv = template.content;
while (tempDiv.firstChild) {
document.body.appendChild(tempDiv.firstChild);
}

View File

@@ -57,10 +57,13 @@ body {
align-items:center;
gap:8px;
}
.logo {
.logos {
width:32px;
height:32px;
}
.logo {
display: none !important;
}
.search-title {
color:var(--text-primary);
text-align:center;

View File

@@ -47,7 +47,7 @@ async function applyFirkaStyling() {
const logoImg = document.createElement('img');
logoImg.src = chrome.runtime.getURL('images/firka_logo.png');
logoImg.alt = 'Firka';
logoImg.className = 'logo';
logoImg.className = 'logos';
logoText.appendChild(logoImg);
logoText.appendChild(document.createTextNode('Firka'));

File diff suppressed because it is too large Load Diff

View File

@@ -22,115 +22,222 @@
</p>
</header>
<div class="settings-card">
<h2 data-i18n="settings.title">Beállítások</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>
<div class="theme-grid">
<button class="theme-option" data-theme="light-green">
<div class="theme-preview light-green">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name" data-i18n="settings.themes.light_green">Világos Zöld</span>
</button>
<div class="tab-navigation">
<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>
<span data-i18n="settings.tabs.settings">Beállítások</span>
</button>
<button class="tab-button" data-tab="about">
<span class="material-icons-round">info</span>
<span data-i18n="settings.tabs.about">Névjegy</span>
</button>
</div>
<button class="theme-option" data-theme="dark-green">
<div class="theme-preview dark-green">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
</div>
<div class="tab-content active" id="tab-appearance">
<div class="settings-card">
<div class="settings-group">
<div class="setting-section">
<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>
<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>
<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 custom-themes-grid" id="customThemesGrid">
<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>
<div class="preview-content">
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name" data-i18n="settings.themes.light_green">Világos Zöld</span>
</button>
<button class="theme-option" data-theme="dark-green">
<div class="theme-preview dark-green">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name" data-i18n="settings.themes.dark_green">Sötét Zöld</span>
</button>
</div>
</div>
<div class="setting-section">
<div class="setting-header">
<span class="material-icons-round">language</span>
<span data-i18n="settings.language">Nyelv</span>
</div>
<div class="language-grid">
<button class="language-option" data-language="hu">
<span class="language-name" data-i18n="settings.languages.hu">Magyar</span>
</button>
<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>
<div class="setting-section">
<div class="setting-header">
<span class="material-icons-round">language</span>
<span data-i18n="settings.language">Nyelv</span>
</div>
<a href="https://folio.zan1456.dev" target="_blank" class="folio-ad-card">
<div class="folio-ad-left">
<img src="../images/folio.png" alt="Folio" class="folio-ad-logo">
<div class="folio-ad-text">
<span class="folio-ad-title">Folio</span>
<span class="folio-ad-desc">Material You KRÉTA app Androidra</span>
</div>
<div class="language-grid">
<button class="language-option" data-language="hu">
<span class="language-name" data-i18n="settings.languages.hu">Magyar</span>
</button>
<button class="language-option" data-language="en">
<span class="language-name" data-i18n="settings.languages.en">English</span>
</button>
</div>
<div class="folio-ad-right">
<span class="material-icons-round">download</span>
<span class="folio-ad-cta">Letöltés</span>
</div>
</a>
</div>
<div class="tab-content" id="tab-settings">
<div class="settings-card">
<h2 data-i18n="settings.page_settings.title">Oldal beállítások</h2>
<div class="page-settings-info">
<div class="current-page-indicator">
<span class="material-icons-round">web</span>
<span id="currentPageName" data-i18n="settings.page_settings.current_page">Aktuális oldal</span>
<span id="currentPageValue" class="page-badge">-</span>
</div>
</div>
<div id="pageSpecificSettings" class="page-specific-settings">
<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>
</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>
<div class="about-card">
<h2 data-i18n="settings.about.title">Névjegy</h2>
<div class="about-content">
<p data-i18n="settings.about.description">A Firka egy nyílt forráskódú projekt, amely a KRÉTA rendszerhez készít saját felhasználói felületet.</p>
<div class="about-links">
<a href="https://firka.app" target="_blank" class="about-link">
<span class="material-icons-round">language</span>
<span>Weboldal</span>
</a>
<a href="https://github.com/QwIT-Development/" target="_blank" class="about-link">
<span class="material-icons-round">code</span>
<span data-i18n="settings.about.github">GitHub</span>
</a>
<a href="https://discord.gg/firka-1111649116020285532" target="_blank" class="about-link">
<span class="material-icons-round">forum</span>
<span>Discord</span>
</a>
<div class="tab-content" id="tab-about">
<div class="about-card">
<h2 data-i18n="settings.about.title">Névjegy</h2>
<div class="about-content">
<p data-i18n="settings.about.description">A Firka egy nyílt forráskódú projekt, amely a KRÉTA rendszerhez készít saját felhasználói felületet.</p>
<div class="about-links">
<a href="https://firka.app" target="_blank" class="about-link">
<span class="material-icons-round">language</span>
<span>Weboldal</span>
</a>
<a href="https://github.com/QwIT-Development/" target="_blank" class="about-link">
<span class="material-icons-round">code</span>
<span data-i18n="settings.about.github">GitHub</span>
</a>
<a href="https://discord.gg/firka-1111649116020285532" target="_blank" class="about-link">
<span class="material-icons-round">forum</span>
<span>Discord</span>
</a>
</div>
</div>
</div>
</div>
<div class="support-card">
<h2 data-i18n="settings.support.title">Támogatás</h2>
<div class="support-content">
<p data-i18n="settings.support.description">Ha tetszik a munkánk és szeretnéd támogatni a fejlesztést, az alábbi módon teheted meg:</p>
<div class="support-buttons">
<a href="https://ko-fi.com/zan1456" target="_blank" class="support-button">
<span class="material-icons-round">coffee</span>
<span data-i18n="settings.support.kofi">Ko-Fi</span>
</a>
<div class="support-card">
<h2 data-i18n="settings.support.title">Támogatás</h2>
<div class="support-content">
<p data-i18n="settings.support.description">Ha tetszik a munkánk és szeretnéd támogatni a fejlesztést, az alábbi módon teheted meg:</p>
<div class="support-buttons">
<a href="https://ko-fi.com/zan1456" target="_blank" class="support-button">
<span class="material-icons-round">coffee</span>
<span data-i18n="settings.support.kofi">Ko-Fi</span>
</a>
</div>
</div>
</div>
<div class="version-info" id="version">v1.4.0</div>
</div>
<footer class="popup-footer">
<div class="version-info" id="version">v1.3.0</div>
</footer>
</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">
<h3 id="themeEditorTitle" data-i18n="settings.custom_themes.create">Új téma létrehozása</h3>
<button class="modal-close" id="closeThemeEditor">
<span class="material-icons-round">close</span>
</button>
</div>
<div class="modal-body">
<div class="theme-form">
@@ -201,9 +308,6 @@
<div class="modal-content share-modal">
<div class="modal-header">
<h3 data-i18n="settings.custom_themes.share">Téma megosztása</h3>
<button class="modal-close" id="closeShareModal">
<span class="material-icons-round">close</span>
</button>
</div>
<div class="modal-body">
<p data-i18n="settings.custom_themes.share_description">Másold ki a kódot és oszd meg másokkal:</p>
@@ -224,9 +328,6 @@
<div class="modal-content import-modal">
<div class="modal-header">
<h3 data-i18n="settings.custom_themes.import">Téma importálása</h3>
<button class="modal-close" id="closeImportModal">
<span class="material-icons-round">close</span>
</button>
</div>
<div class="modal-body">
<p data-i18n="settings.custom_themes.import_description">Illeszd be a téma kódot:</p>
@@ -240,6 +341,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

@@ -5,6 +5,349 @@ document.addEventListener("DOMContentLoaded", async () => {
let customThemes = [];
let editingThemeId = null;
let currentPageType = null;
async function initTabs() {
const tabButtons = document.querySelectorAll(".tab-button");
const tabContents = document.querySelectorAll(".tab-content");
const lastTab = localStorage.getItem("settingsLastTab") || "appearance";
switchTab(lastTab);
tabButtons.forEach(button => {
button.addEventListener("click", () => {
const tabId = button.dataset.tab;
switchTab(tabId);
localStorage.setItem("settingsLastTab", tabId);
});
});
}
function switchTab(tabId) {
const tabButtons = document.querySelectorAll(".tab-button");
const tabContents = document.querySelectorAll(".tab-content");
tabButtons.forEach(btn => {
btn.classList.toggle("active", btn.dataset.tab === tabId);
});
tabContents.forEach(content => {
content.classList.toggle("active", content.id === `tab-${tabId}`);
});
if (tabId === "settings") {
detectCurrentPage();
}
}
async function detectCurrentPage() {
try {
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
if (tabs.length > 0) {
const url = tabs[0].url || "";
currentPageType = getPageTypeFromUrl(url);
updateCurrentPageIndicator(currentPageType);
await loadPageSpecificSettings(currentPageType);
}
} catch (error) {
console.error("Error detecting current page:", error);
currentPageType = "unknown";
updateCurrentPageIndicator(currentPageType);
await loadPageSpecificSettings(currentPageType);
}
}
function getPageTypeFromUrl(url) {
if (url.includes("/Orarend/InformaciokOrarend")) return "timetable";
if (url.includes("/TanuloErtekeles/Osztalyzatok")) return "grades";
if (url.includes("/Hianyzas/Hianyzasok")) return "absences";
if (url.includes("/uzenetek")) return "messages";
if (url.includes("/Adminisztracio/Profil")) return "profile";
if (url.includes("intezmenykereso.e-kreta.hu/")) return "search";
if (url.includes("/Intezmeny/Faliujsag")) return "dashboard";
if (url.includes("/Adminisztracio/BelepesKezelo")) return "roleselect";
if (url.includes("/ElfelejtettJelszo")) return "forgotpassword";
if (url.includes("idp.e-kreta.hu/Account/Login")) return "login";
if (url.includes("/Login") || url.includes("/Belepes")) return "login";
return "unknown";
}
function getPageDisplayName(pageType) {
const pageNames = {
"timetable": "Órarend",
"grades": "Jegyek",
"absences": "Mulasztások",
"messages": "Üzenetek",
"profile": "Profil",
"search": "Intézménykereső",
"dashboard": "Faliújság",
"roleselect": "Szerepkörválasztó",
"login": "Bejelentkezés",
"forgotpassword": "Elfelejtett jelszó",
"unknown": "Ismeretlen"
};
return pageNames[pageType] || pageType;
}
function updateCurrentPageIndicator(pageType) {
const pageValueEl = document.getElementById("currentPageValue");
if (pageValueEl) {
pageValueEl.textContent = getPageDisplayName(pageType);
}
}
async function getLoginPageSettings() {
const settingsKey = "pageSettings_login";
const savedSettings = await storageManager.get(settingsKey, {});
return [
{
key: "hideSystemMessage",
type: "toggle",
label: "Rendszerüzenet elrejtése",
labelKey: "settings.page_settings.login.hide_system_message",
description: "A bejelentkezési oldalon megjelenő rendszerüzenet elrejtése",
descriptionKey: "settings.page_settings.login.hide_system_message_desc",
value: savedSettings.hideSystemMessage || false
},
{
key: "hideSchoolInfo",
type: "toggle",
label: "Iskola nevének és azonosítójának elrejtése",
labelKey: "settings.page_settings.login.hide_school_info",
description: "Az iskola neve és KRÉTA azonosítója nem jelenik meg",
descriptionKey: "settings.page_settings.login.hide_school_info_desc",
value: savedSettings.hideSchoolInfo || false
}
];
}
async function getRoleselectPageSettings() {
const settingsKey = "pageSettings_roleselect";
const savedSettings = await storageManager.get(settingsKey, {
autoRedirect: false,
hideSchoolInfo: true
});
return [
{
key: "autoRedirect",
type: "toggle",
label: "Automatikus továbblépés",
labelKey: "settings.page_settings.roleselect.auto_redirect",
description: "Automatikusan átirányít az ellenőrzőkönyvre",
descriptionKey: "settings.page_settings.roleselect.auto_redirect_desc",
value: savedSettings.autoRedirect || false
},
{
key: "hideSchoolInfo",
type: "toggle",
label: "Iskola és név elrejtése",
labelKey: "settings.page_settings.roleselect.hide_school_info",
description: "Az iskola neve és a felhasználó neve nem jelenik meg",
descriptionKey: "settings.page_settings.roleselect.hide_school_info_desc",
value: savedSettings.hideSchoolInfo !== undefined ? savedSettings.hideSchoolInfo : true
}
];
}
async function getBulletinPageSettings() {
const settingsKey = "pageSettings_bulletin";
const savedSettings = await storageManager.get(settingsKey, {});
return [
{
key: "hideGrades",
type: "toggle",
label: "Értékelések elrejtése",
labelKey: "settings.page_settings.bulletin.hide_grades",
description: "Az értékeléseid kártya elrejtése",
descriptionKey: "settings.page_settings.bulletin.hide_grades_desc",
value: savedSettings.hideGrades || false
},
{
key: "hideAbsences",
type: "toggle",
label: "Mulasztások elrejtése",
labelKey: "settings.page_settings.bulletin.hide_absences",
description: "A mulasztások kártya elrejtése",
descriptionKey: "settings.page_settings.bulletin.hide_absences_desc",
value: savedSettings.hideAbsences || false
},
{
key: "hideNotes",
type: "toggle",
label: "Feljegyzések elrejtése",
labelKey: "settings.page_settings.bulletin.hide_notes",
description: "A feljegyzések kártya elrejtése",
descriptionKey: "settings.page_settings.bulletin.hide_notes_desc",
value: savedSettings.hideNotes || false
},
{
key: "hideExams",
type: "toggle",
label: "Bejelentett dolgozatok elrejtése",
labelKey: "settings.page_settings.bulletin.hide_exams",
description: "A bejelentett dolgozatok kártya elrejtése",
descriptionKey: "settings.page_settings.bulletin.hide_exams_desc",
value: savedSettings.hideExams || false
}
];
}
async function getGradesPageSettings() {
const settingsKey = "pageSettings_grades";
const savedSettings = await storageManager.get(settingsKey, {});
return [
{
key: "hideChart",
type: "toggle",
label: "Grafikon elrejtése",
labelKey: "settings.page_settings.grades.hide_chart",
description: "A jegyek grafikonjának elrejtése",
descriptionKey: "settings.page_settings.grades.hide_chart_desc",
value: savedSettings.hideChart || false
},
{
key: "hideClassAverage",
type: "toggle",
label: "Osztályátlag elrejtése",
labelKey: "settings.page_settings.grades.hide_class_average",
description: "Az osztályátlag értékek elrejtése",
descriptionKey: "settings.page_settings.grades.hide_class_average_desc",
value: savedSettings.hideClassAverage || false
}
];
}
async function loadPageSpecificSettings(pageType) {
const container = document.getElementById("pageSpecificSettings");
if (!container) return;
const pageSettings = {
"timetable": [],
"grades": await getGradesPageSettings(),
"absences": [],
"messages": [],
"profile": [],
"search": [],
"dashboard": await getBulletinPageSettings(),
"roleselect": await getRoleselectPageSettings(),
"login": await getLoginPageSettings(),
"forgotpassword": [],
"unknown": []
};
const settings = pageSettings[pageType] || [];
if (settings.length === 0) {
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;
}
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) {
window.LanguageManager.loadTranslationsForPage();
}
}
function renderSettingItem(setting) {
let control = "";
switch (setting.type) {
case "toggle":
control = `
<label class="toggle-switch">
<input type="checkbox" data-setting="${setting.key}" ${setting.value ? "checked" : ""}>
<span class="toggle-slider"></span>
</label>
`;
break;
case "select":
control = `
<select class="setting-select" data-setting="${setting.key}">
${setting.options.map(opt =>
`<option value="${opt.value}" ${opt.value === setting.value ? "selected" : ""}>${opt.label}</option>`
).join("")}
</select>
`;
break;
default:
control = "";
}
return `
<div class="setting-item" data-setting-key="${setting.key}">
<div class="setting-item-info">
<div class="setting-item-label" data-i18n="${setting.labelKey || ""}">${setting.label}</div>
${setting.description ? `<div class="setting-item-description" data-i18n="${setting.descriptionKey || ""}">${setting.description}</div>` : ""}
</div>
${control}
</div>
`;
}
function initSettingItems(container) {
container.querySelectorAll('input[type="checkbox"]').forEach(input => {
input.addEventListener("change", async () => {
const key = input.dataset.setting;
await savePageSetting(currentPageType, key, input.checked);
});
});
container.querySelectorAll(".setting-select").forEach(select => {
select.addEventListener("change", async () => {
const key = select.dataset.setting;
await savePageSetting(currentPageType, key, select.value);
});
});
}
async function savePageSetting(pageType, key, value) {
try {
const storagePageType = pageType === "dashboard" ? "bulletin" : pageType;
const settingsKey = `pageSettings_${storagePageType}`;
const existingSettings = await storageManager.get(settingsKey, {});
existingSettings[key] = value;
await storageManager.set(settingsKey, existingSettings);
const tabs = await chrome.tabs.query({});
tabs.forEach((tab) => {
chrome.tabs
.sendMessage(tab.id, {
action: "pageSettingChanged",
pageType: storagePageType,
key: key,
value: value,
})
.catch(() => {});
});
} catch (error) {
console.error("Error saving page setting:", error);
}
}
async function loadCustomThemes() {
try {
@@ -26,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) {
@@ -224,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) {
@@ -337,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) {
@@ -384,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);
@@ -396,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));
}
@@ -403,7 +818,7 @@ document.addEventListener("DOMContentLoaded", async () => {
const root = document.documentElement;
const properties = [
"--background",
"--card-card",
"--card-card",
"--accent-accent",
"--text-primary",
"--text-secondary",
@@ -413,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));
}
@@ -498,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");
});
@@ -603,4 +1045,124 @@ document.addEventListener("DOMContentLoaded", async () => {
window.addEventListener("languageChanged", (event) => {
updateLanguageButtons(event.detail.language);
});
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');
}

462
setup/setup.css Normal file
View File

@@ -0,0 +1,462 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Montserrat', sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 1rem;
background: var(--background);
color: var(--text-primary);
}
.setup-container {
width: 100%;
max-width: 700px;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.setup-card {
background: var(--card-card);
border-radius: 20px;
padding: 2rem;
box-shadow: 0 4px 20px var(--accent-shadow);
}
.setup-header {
text-align: center;
margin-bottom: 2rem;
}
.setup-logo {
width: 60px;
height: 60px;
margin-bottom: 1rem;
}
.setup-title {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.5rem;
}
.setup-subtitle {
font-size: 1rem;
color: var(--text-secondary);
}
.progress-bar {
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 2rem;
padding: 0 1rem;
}
.progress-step {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.4rem;
position: relative;
}
.progress-circle {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--button-secondaryFill);
border: 2px solid var(--text-teritary);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 1rem;
color: var(--text-teritary);
transition: all 0.3s ease;
}
.progress-step.active .progress-circle {
background: var(--accent-accent);
border-color: var(--accent-accent);
color: white;
transform: scale(1.1);
}
.progress-step.completed .progress-circle {
background: var(--accent-accent);
border-color: var(--accent-accent);
color: white;
}
.progress-label {
font-size: 0.875rem;
color: var(--text-secondary);
font-weight: 500;
}
.progress-step.active .progress-label {
color: var(--accent-accent);
font-weight: 600;
}
.progress-line {
width: 80px;
height: 2px;
background: var(--text-teritary);
margin: 0 0.5rem;
margin-bottom: 1.5rem;
}
.setup-content {
min-height: 320px;
position: relative;
}
.setup-step {
display: none;
animation: slideIn 0.4s ease;
}
.setup-step.active {
display: block;
}
@keyframes slideIn {
from {
opacity: 0;
transform: translateX(20px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
.step-icon {
text-align: center;
margin-bottom: 1rem;
}
.step-icon .material-icons-round {
font-size: 3rem;
color: var(--accent-accent);
}
.step-icon.success .material-icons-round {
font-size: 3.5rem;
color: var(--success);
}
.step-title {
text-align: center;
font-size: 1.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 0.4rem;
}
.step-description {
text-align: center;
font-size: 0.9rem;
color: var(--text-secondary);
margin-bottom: 1.5rem;
}
.theme-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.theme-card {
background: var(--button-secondaryFill);
border: 3px solid transparent;
border-radius: 12px;
padding: 1rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.8rem;
position: relative;
}
.theme-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px var(--accent-shadow);
}
.theme-card.selected {
border-color: var(--accent-accent);
background: var(--accent-15);
}
.theme-icon {
position: absolute;
top: 1rem;
right: 1rem;
color: var(--text-secondary);
font-size: 1.5rem;
}
.theme-card.selected .theme-icon {
color: var(--accent-accent);
}
.theme-preview {
width: 100%;
height: 100px;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.theme-preview.light-green {
background: #FAFFF0;
}
.theme-preview.dark-green {
background: #0D1202;
}
.preview-header {
height: 30%;
display: flex;
align-items: center;
padding: 0.5rem;
}
.theme-preview.light-green .preview-header {
background: #F3FBDE;
}
.theme-preview.dark-green .preview-header {
background: #141905;
}
.preview-content {
height: 70%;
padding: 0.5rem;
}
.preview-card {
width: 100%;
height: 100%;
border-radius: 6px;
}
.theme-preview.light-green .preview-card {
background: white;
}
.theme-preview.dark-green .preview-card {
background: #1a2207;
}
.theme-name {
font-weight: 600;
color: var(--text-primary);
font-size: 1.1rem;
}
.language-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.language-card {
background: var(--button-secondaryFill);
border: 3px solid transparent;
border-radius: 12px;
padding: 1.5rem;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.8rem;
}
.language-card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px var(--accent-shadow);
}
.language-card.selected {
border-color: var(--accent-accent);
background: var(--accent-15);
}
.language-flag {
font-size: 3rem;
}
.language-name {
font-weight: 600;
color: var(--text-primary);
font-size: 1.1rem;
}
.finish-links {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 0.8rem;
margin-top: 1rem;
}
.finish-link {
background: var(--button-secondaryFill);
border: 2px solid transparent;
border-radius: 10px;
padding: 1rem;
display: flex;
align-items: center;
gap: 0.8rem;
text-decoration: none;
color: var(--text-primary);
transition: all 0.3s ease;
}
.finish-link:hover {
border-color: var(--accent-accent);
background: var(--accent-15);
transform: translateY(-2px);
}
.finish-link .material-icons-round {
font-size: 2rem;
color: var(--accent-accent);
flex-shrink: 0;
}
.link-content {
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.link-title {
font-weight: 600;
font-size: 1rem;
color: var(--text-primary);
}
.link-description {
font-size: 0.8rem;
color: var(--text-secondary);
}
.setup-actions {
display: flex;
justify-content: space-between;
margin-top: 2rem;
gap: 1rem;
}
.actions-spacer {
flex: 1;
}
.btn-primary,
.btn-secondary {
padding: 1rem 2rem;
border-radius: 12px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
border: none;
display: flex;
align-items: center;
gap: 0.5rem;
font-family: 'Montserrat', sans-serif;
}
.btn-primary {
background: var(--accent-accent);
color: white;
}
.btn-primary:hover {
background: var(--accent-secondary);
transform: translateY(-2px);
box-shadow: 0 4px 12px var(--accent-shadow);
}
.btn-secondary {
background: var(--button-secondaryFill);
color: var(--text-primary);
border: 2px solid var(--text-teritary);
}
.btn-secondary:hover {
border-color: var(--accent-accent);
transform: translateY(-2px);
}
.btn-primary .material-icons-round,
.btn-secondary .material-icons-round {
font-size: 1.25rem;
}
@media (max-width: 768px) {
body {
padding: 0.5rem;
}
.setup-card {
padding: 1.5rem;
}
.setup-title {
font-size: 1.75rem;
}
.step-title {
font-size: 1.3rem;
}
.progress-bar {
padding: 0;
}
.progress-line {
width: 30px;
}
.progress-circle {
width: 36px;
height: 36px;
}
.theme-options,
.language-options {
grid-template-columns: 1fr;
}
.finish-links {
grid-template-columns: 1fr;
}
}

159
setup/setup.html Normal file
View File

@@ -0,0 +1,159 @@
<!DOCTYPE html>
<html lang="hu">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Firxa - Kezdeti beállítások</title>
<link rel="icon" type="image/png" href="../images/firka_logo_128.png">
<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">
<link rel="stylesheet" href="setup.css">
<link rel="stylesheet" href="../global/theme.css">
</head>
<body>
<div class="setup-container">
<div class="setup-card">
<div class="setup-header">
<img src="../images/firka_logo.png" alt="Firka" class="setup-logo">
<h1 class="setup-title">Üdvözöl a Firxa!</h1>
<p class="setup-subtitle" data-i18n="setup.welcome">Állítsd be a bővítményt néhány egyszerű lépésben</p>
</div>
<div class="progress-bar">
<div class="progress-step active" data-step="1">
<div class="progress-circle">1</div>
<span class="progress-label" data-i18n="setup.steps.theme">Téma</span>
</div>
<div class="progress-line"></div>
<div class="progress-step" data-step="2">
<div class="progress-circle">2</div>
<span class="progress-label" data-i18n="setup.steps.language">Nyelv</span>
</div>
<div class="progress-line"></div>
<div class="progress-step" data-step="3">
<div class="progress-circle">3</div>
<span class="progress-label" data-i18n="setup.steps.finish">Kész</span>
</div>
</div>
<div class="setup-content">
<div class="setup-step active" data-step="1">
<div class="step-icon">
<span class="material-icons-round">palette</span>
</div>
<h2 class="step-title" data-i18n="setup.theme.title">Válassz témát</h2>
<p class="step-description" data-i18n="setup.theme.description">Válaszd ki a számodra legmegfelelőbb megjelenést</p>
<div class="theme-options">
<button class="theme-card" data-theme="light-green">
<div class="theme-preview light-green">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name" data-i18n="settings.themes.light_green">Világos Zöld</span>
<span class="theme-icon material-icons-round">light_mode</span>
</button>
<button class="theme-card" data-theme="dark-green">
<div class="theme-preview dark-green">
<div class="preview-header"></div>
<div class="preview-content">
<div class="preview-card"></div>
</div>
</div>
<span class="theme-name" data-i18n="settings.themes.dark_green">Sötét Zöld</span>
<span class="theme-icon material-icons-round">dark_mode</span>
</button>
</div>
</div>
<div class="setup-step" data-step="2">
<div class="step-icon">
<span class="material-icons-round">language</span>
</div>
<h2 class="step-title" data-i18n="setup.language.title">Válassz nyelvet</h2>
<p class="step-description" data-i18n="setup.language.description">Válaszd ki a használni kívánt nyelvet</p>
<div class="language-options">
<button class="language-card" data-language="hu">
<span class="language-flag">🇭🇺</span>
<span class="language-name" data-i18n="settings.languages.hu">Magyar</span>
</button>
<button class="language-card" data-language="en">
<span class="language-flag">🇬🇧</span>
<span class="language-name" data-i18n="settings.languages.en">English</span>
</button>
</div>
</div>
<div class="setup-step" data-step="3">
<div class="step-icon success">
<span class="material-icons-round">check_circle</span>
</div>
<h2 class="step-title" data-i18n="setup.finish.title">Minden kész!</h2>
<p class="step-description" data-i18n="setup.finish.description">A beállítások sikeresen mentve. Indulhat a tanulás!</p>
<div class="finish-links">
<a href="https://firka.app" target="_blank" class="finish-link">
<span class="material-icons-round">info</span>
<div class="link-content">
<span class="link-title" data-i18n="setup.finish.about">Weboldal</span>
<span class="link-description" data-i18n="setup.finish.about_desc">Tudj meg többet a projektről</span>
</div>
</a>
<a href="https://ko-fi.com/zan1456" target="_blank" class="finish-link">
<span class="material-icons-round">favorite</span>
<div class="link-content">
<span class="link-title" data-i18n="setup.finish.support">Támogatás</span>
<span class="link-description" data-i18n="setup.finish.support_desc">Támogasd a fejlesztést</span>
</div>
</a>
<a href="https://github.com/QwIT-Development/" target="_blank" class="finish-link">
<span class="material-icons-round">code</span>
<div class="link-content">
<span class="link-title" data-i18n="setup.finish.github">GitHub</span>
<span class="link-description" data-i18n="setup.finish.github_desc">Nézd meg a forráskódot</span>
</div>
</a>
<a href="https://discord.gg/firka-1111649116020285532" target="_blank" class="finish-link">
<span class="material-icons-round">forum</span>
<div class="link-content">
<span class="link-title" data-i18n="setup.finish.discord">Discord</span>
<span class="link-description" data-i18n="setup.finish.discord_desc">Csatlakozz a közösséghez</span>
</div>
</a>
</div>
</div>
</div>
<div class="setup-actions">
<button class="btn-secondary" id="backBtn" style="display: none;">
<span class="material-icons-round">arrow_back</span>
<span data-i18n="common.back">Vissza</span>
</button>
<div class="actions-spacer"></div>
<button class="btn-primary" id="nextBtn">
<span data-i18n="common.next">Tovább</span>
<span class="material-icons-round">arrow_forward</span>
</button>
<button class="btn-primary" id="finishBtn" style="display: none;">
<span data-i18n="setup.finish.start">Kezdés</span>
<span class="material-icons-round">check</span>
</button>
</div>
</div>
</div>
<script src="../tools/storageManager.js"></script>
<script src="../global/language.js"></script>
<script src="setup.js"></script>
</body>
</html>

146
setup/setup.js Normal file
View File

@@ -0,0 +1,146 @@
let currentStep = 1;
const totalSteps = 3;
let selectedTheme = 'light-green';
let selectedLanguage = 'hu';
document.addEventListener('DOMContentLoaded', async () => {
selectedTheme = await storageManager.get('themePreference', 'light-green');
selectedLanguage = await storageManager.get('languagePreference', 'hu');
initializeThemeSelection();
initializeLanguageSelection();
document.getElementById('nextBtn').addEventListener('click', nextStep);
document.getElementById('backBtn').addEventListener('click', previousStep);
document.getElementById('finishBtn').addEventListener('click', finishSetup);
document.querySelectorAll('.theme-card').forEach(card => {
card.addEventListener('click', () => selectTheme(card.dataset.theme));
});
document.querySelectorAll('.language-card').forEach(card => {
card.addEventListener('click', () => selectLanguage(card.dataset.language));
});
applyTheme(selectedTheme);
});
function initializeThemeSelection() {
const themeCards = document.querySelectorAll('.theme-card');
themeCards.forEach(card => {
if (card.dataset.theme === selectedTheme) {
card.classList.add('selected');
}
});
}
function initializeLanguageSelection() {
const languageCards = document.querySelectorAll('.language-card');
languageCards.forEach(card => {
if (card.dataset.language === selectedLanguage) {
card.classList.add('selected');
}
});
}
function selectTheme(theme) {
selectedTheme = theme;
document.querySelectorAll('.theme-card').forEach(card => {
card.classList.remove('selected');
});
document.querySelector(`[data-theme="${theme}"]`).classList.add('selected');
applyTheme(theme);
}
function selectLanguage(language) {
selectedLanguage = language;
document.querySelectorAll('.language-card').forEach(card => {
card.classList.remove('selected');
});
document.querySelector(`[data-language="${language}"]`).classList.add('selected');
}
function applyTheme(theme) {
document.documentElement.setAttribute('data-theme', theme);
}
function nextStep() {
if (currentStep < totalSteps) {
saveCurrentStepSettings();
currentStep++;
updateStepDisplay();
}
}
function previousStep() {
if (currentStep > 1) {
currentStep--;
updateStepDisplay();
}
}
async function saveCurrentStepSettings() {
if (currentStep === 1) {
await storageManager.set('themePreference', selectedTheme);
} else if (currentStep === 2) {
await storageManager.set('language', selectedLanguage);
await storageManager.set('languagePreference', selectedLanguage);
if (window.LanguageManager) {
await window.LanguageManager.changeLanguage(selectedLanguage);
}
}
}
function updateStepDisplay() {
document.querySelectorAll('.progress-step').forEach(step => {
const stepNumber = parseInt(step.dataset.step);
if (stepNumber < currentStep) {
step.classList.add('completed');
step.classList.remove('active');
} else if (stepNumber === currentStep) {
step.classList.add('active');
step.classList.remove('completed');
} else {
step.classList.remove('active', 'completed');
}
});
document.querySelectorAll('.setup-step').forEach(step => {
if (parseInt(step.dataset.step) === currentStep) {
step.classList.add('active');
} else {
step.classList.remove('active');
}
});
const backBtn = document.getElementById('backBtn');
const nextBtn = document.getElementById('nextBtn');
const finishBtn = document.getElementById('finishBtn');
if (currentStep === 1) {
backBtn.style.display = 'none';
} else {
backBtn.style.display = 'flex';
}
if (currentStep === totalSteps) {
nextBtn.style.display = 'none';
finishBtn.style.display = 'flex';
} else {
nextBtn.style.display = 'flex';
finishBtn.style.display = 'none';
}
}
async function finishSetup() {
await saveCurrentStepSettings();
await storageManager.set('setupCompleted', true);
window.location.href = 'https://intezmenykereso.e-kreta.hu/';
}

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,16 @@
}
}
async function loadTestDetailsFromAPI(testId) {
let cachedTestData = null;
let testDataTimestamp = 0;
const TEST_DATA_CACHE_DURATION = 60000;
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 +336,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 +367,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 +434,7 @@
originalTeacher: "",
room: "",
day: dayIndex,
date: weekDates[dayIndex]?.fullDate || eventDate.toISOString().split('T')[0],
isSubstituted: false,
isCancelled: false,
hasHomework: false,
@@ -445,9 +471,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 +482,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 +523,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 +539,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 +624,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 +715,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 +757,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 +767,8 @@
})()
}
</div>
`,
)
`;
})
.join("")
}
</div>
@@ -887,18 +1011,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 +1041,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 +1319,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 +1412,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 +1427,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 +1437,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 +1454,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 +1467,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 +1479,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 +1487,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 +1504,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 +1785,7 @@
modalContent.appendChild(header);
modalContent.appendChild(body);
modal.innerHTML = '';
helper.clearElement(modal);
modal.appendChild(modalContent);
document.body.appendChild(modal);
@@ -1703,11 +1856,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 +2022,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 +2357,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 +2445,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 +2492,7 @@
};
document.body.innerHTML = '';
helper.clearElement(document.body);
const kretaContainer = document.createElement('div');
@@ -2330,9 +2501,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 +2651,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 +2712,7 @@
}
weekDisplay.innerHTML = '';
helper.clearElement(weekDisplay);
visibleWeeks.forEach((weekNum, index) => {
const isSelected = index === 2;
const isCurrent = weekNum === currentWeekNumber;
@@ -2637,12 +2808,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 +2841,7 @@
}
modalGrid.innerHTML = '';
helper.clearElement(modalGrid);
allWeeks.forEach((weekNumber) => {
const isSelected = weekNumber === selectedWeekNumber;
const isCurrent = weekNumber === currentWeekNumber;

View File

@@ -1,3 +1,15 @@
chrome.runtime.onInstalled.addListener(async (details) => {
if (details.reason === 'install') {
const setupCompleted = await chrome.storage.sync.get('firka_setupCompleted');
if (!setupCompleted.firka_setupCompleted) {
chrome.tabs.create({
url: chrome.runtime.getURL('setup/setup.html')
});
}
}
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
(async () => {
try {
@@ -22,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' });
@@ -67,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);
}
@@ -77,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);
}
}
};