14 Commits
1.4.4 ... 1.4.6

Author SHA1 Message Date
Zan
a94c06d893 Dupla óra 2026-01-15 19:10:30 +01:00
Zan
7cd8c065b3 fix 2026-01-15 17:59:19 +01:00
Zan
d44be3b029 Message attachments 2026-01-15 17:56:40 +01:00
Zan
3243ebdc2e Merge branch 'main' of https://github.com/QwIT-Development/firka-extension 2026-01-15 17:43:44 +01:00
Zan
0f69a1583b Online ora 2026-01-15 17:42:53 +01:00
Zan
15ab433064 Sy removed 2026-01-15 17:23:09 +01:00
Zan
4aab9afebd message html fix 2026-01-15 17:18:19 +01:00
Zan
e0df578dca Update logo image source in README.md 2026-01-15 17:10:59 +01:00
Zan
dd320d3dc4 Online óra 2026-01-15 17:07:59 +01:00
Zan
5248cfc12e Revert "#23 fix"
This reverts commit 1429943dbe.
2026-01-15 16:27:36 +01:00
Zan
1f34cacf25 updater fix 2026-01-12 20:34:48 +01:00
Zan
f35d29f56c fix custom homework 2026-01-12 20:09:41 +01:00
Zan
1429943dbe #23 fix 2026-01-12 19:53:35 +01:00
Zan
2ca1c9949a updater 2026-01-12 19:33:19 +01:00
16 changed files with 715 additions and 310 deletions

View File

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

1
icons/contact.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 353 B

1
icons/project.svg Normal file
View File

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

After

Width:  |  Height:  |  Size: 365 B

View File

@@ -40,9 +40,7 @@
"grades/chart.js",
"i18n/*.json",
"tools/storageManager.js",
"tools/storageTest.js",
"tools/sentry.js",
"tools/sentry-browser.min.js"
"tools/storageTest.js"
],
"matches": [
"<all_urls>"
@@ -67,8 +65,6 @@
"https://eugyintezes.e-kreta.hu/*"
],
"js": [
"tools/sentry-browser.min.js",
"tools/sentry.js",
"global/language.js",
"global/theme.js",
"tools/loadingScreen.js",

View File

@@ -40,9 +40,7 @@
"grades/chart.js",
"i18n/*.json",
"tools/storageManager.js",
"tools/storageTest.js",
"tools/sentry.js",
"tools/sentry-browser.min.js"
"tools/storageTest.js"
],
"matches": [
"<all_urls>"
@@ -67,8 +65,6 @@
"https://eugyintezes.e-kreta.hu/*"
],
"js": [
"tools/sentry-browser.min.js",
"tools/sentry.js",
"global/language.js",
"global/theme.js",
"tools/loadingScreen.js",

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

@@ -310,7 +310,8 @@
messageContent.appendChild(contentTitle);
const messageText = document.createElement('div');
messageText.className = 'message-text';
messageText.textContent = content;
messageText.innerHTML = content;
messageContent.appendChild(messageText);
messageDetails.appendChild(messageContent);
@@ -320,14 +321,17 @@
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.nev);
a.onclick = () => downloadAttachment(attachment.azonosito);
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);
});
@@ -345,6 +349,32 @@
}
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;

View File

@@ -593,29 +593,6 @@ h2 {
margin: 0;
}
.modal-close {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 8px;
background: var(--button-secondaryFill);
color: var(--text-secondary);
cursor: pointer;
transition: all 0.2s ease;
}
.modal-close:hover {
background: var(--accent-15);
color: var(--accent-accent);
}
.modal-close .material-icons-round {
font-size: 20px;
}
.modal-body {
padding: 16px;
}
@@ -1150,3 +1127,183 @@ h2 {
opacity: 1;
transform: translateY(0);
}
.update-modal .modal-content {
max-width: 500px;
}
.update-version-info {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 20px;
padding: 16px;
background: var(--button-secondaryFill);
border-radius: 12px;
}
.version-badge {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
}
.version-badge.current {
text-align: left;
}
.version-badge.latest {
text-align: right;
}
.version-label {
font-size: 11px;
color: var(--text-secondary);
font-weight: 500;
}
.version-number {
font-size: 16px;
font-weight: 700;
color: var(--text-primary);
font-family: monospace;
}
.version-badge.latest .version-number {
color: var(--accent-accent);
}
.version-arrow {
color: var(--accent-accent);
font-size: 24px;
flex-shrink: 0;
}
.update-changelog-section {
margin-top: 16px;
}
.update-changelog-section h4 {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
margin: 0 0 12px 0;
}
.update-changelog {
max-height: 300px;
overflow-y: auto;
padding: 12px;
background: var(--button-secondaryFill);
border-radius: 8px;
font-size: 13px;
line-height: 1.6;
color: var(--text-secondary);
}
.update-changelog p {
margin: 0 0 8px 0;
color: var(--text-primary);
}
.update-changelog p:last-child {
margin-bottom: 0;
}
.update-changelog h1,
.update-changelog h2,
.update-changelog h3 {
color: var(--text-primary);
margin: 12px 0 8px 0;
font-weight: 600;
}
.update-changelog h1 {
font-size: 18px;
}
.update-changelog h2 {
font-size: 16px;
}
.update-changelog h3 {
font-size: 14px;
}
.update-changelog ul,
.update-changelog ol {
margin: 8px 0;
padding-left: 24px;
color: var(--text-primary);
}
.update-changelog li {
margin: 4px 0;
}
.update-changelog code {
background: var(--accent-15);
color: var(--accent-accent);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
}
.update-changelog pre {
background: var(--background);
color: var(--text-primary);
padding: 12px;
border-radius: 6px;
overflow-x: auto;
margin: 8px 0;
}
.update-changelog pre code {
background: transparent;
padding: 0;
}
.update-changelog a {
color: var(--accent-accent);
text-decoration: none;
font-weight: 500;
}
.update-changelog a:hover {
text-decoration: underline;
}
.update-changelog blockquote {
border-left: 3px solid var(--accent-accent);
padding-left: 12px;
margin: 8px 0;
color: var(--text-secondary);
font-style: italic;
}
.update-changelog strong {
color: var(--text-primary);
font-weight: 600;
}
.update-changelog em {
font-style: italic;
color: var(--text-secondary);
}
.update-button {
display: flex;
align-items: center;
gap: 6px;
text-decoration: none;
justify-content: center;
border-radius: 8px;
padding: 12px 16px;
}
.update-button .material-icons-round {
font-size: 18px;
}

View File

@@ -188,6 +188,39 @@
</div>
<div class="modal-overlay" id="updateModal">
<div class="modal-content update-modal">
<div class="modal-header">
<h3 data-i18n="settings.update.title">Új verzió érhető el!</h3>
</div>
<div class="modal-body">
<div class="update-version-info">
<div class="version-badge current">
<span class="version-label" data-i18n="settings.update.current_version">Jelenlegi verzió:</span>
<span class="version-number" id="currentVersion">-</span>
</div>
<span class="material-icons-round version-arrow">arrow_forward</span>
<div class="version-badge latest">
<span class="version-label" data-i18n="settings.update.latest_version">Legújabb verzió:</span>
<span class="version-number" id="latestVersion">-</span>
</div>
</div>
<div class="update-changelog-section">
<h4 data-i18n="settings.update.whats_new">Újdonságok:</h4>
<div class="update-changelog" id="updateChangelog">
<p data-i18n="settings.update.loading">Betöltés...</p>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary" id="dismissUpdate" data-i18n="settings.update.dismiss">Bezárás</button>
<a class="btn-primary update-button" id="updateButton" href="#" target="_blank">
<span data-i18n="settings.update.download">Letöltés</span>
</a>
</div>
</div>
</div>
<div class="modal-overlay" id="themeEditorModal">
<div class="modal-content theme-editor-modal">
<div class="modal-header">
@@ -304,8 +337,6 @@
</div>
</div>
<script src="../tools/sentry-browser.min.js"></script>
<script src="../tools/sentry.js"></script>
<script src="../tools/helper.js"></script>
<script src="../tools/storageManager.js"></script>
<script src="../global/language.js"></script>

View File

@@ -1048,6 +1048,7 @@ document.addEventListener("DOMContentLoaded", async () => {
await initTabs();
await initErrorReporting();
await checkForUpdates();
});
async function initErrorReporting() {
@@ -1067,3 +1068,101 @@ async function initErrorReporting() {
});
});
}
async function checkForUpdates() {
try {
const manifest = chrome.runtime.getManifest();
const currentVersion = manifest.version;
const response = await fetch('https://api.github.com/repos/QwIT-Development/firka-extension/releases/latest');
if (!response.ok) {
console.error('Failed to fetch latest release');
return;
}
const latestRelease = await response.json();
const latestVersion = latestRelease.tag_name.replace(/^v/, '');
if (compareVersions(latestVersion, currentVersion) > 0) {
showUpdateModal(currentVersion, latestVersion, latestRelease);
}
} catch (error) {
console.error('Error checking for updates:', error);
}
}
function compareVersions(v1, v2) {
const parts1 = v1.split('.').map(Number);
const parts2 = v2.split('.').map(Number);
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
const part1 = parts1[i] || 0;
const part2 = parts2[i] || 0;
if (part1 > part2) return 1;
if (part1 < part2) return -1;
}
return 0;
}
function parseMarkdown(markdown) {
let html = markdown;
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
html = html.replace(/\*\*\*(.+?)\*\*\*/g, '<strong><em>$1</em></strong>');
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
html = html.replace(/__(.+?)__/g, '<strong>$1</strong>');
html = html.replace(/_(.+?)_/g, '<em>$1</em>');
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
html = html.replace(/```([^```]+)```/g, '<pre><code>$1</code></pre>');
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
html = html.replace(/^> (.+)/gim, '<blockquote>$1</blockquote>');
html = html.replace(/^\* (.+)$/gim, '<li>$1</li>');
html = html.replace(/^- (.+)$/gim, '<li>$1</li>');
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
html = html.replace(/^\d+\. (.+)$/gim, '<li>$1</li>');
html = html.replace(/\n\n/g, '</p><p>');
html = html.replace(/^(?!<[hou]|<li|<pre|<blockquote)(.+)$/gim, '<p>$1</p>');
html = html.replace(/<p><\/p>/g, '');
html = html.replace(/<p>(<[hou])/g, '$1');
html = html.replace(/(<\/[hou]l?>)<\/p>/g, '$1');
return html;
}
function showUpdateModal(currentVersion, latestVersion, releaseData) {
const modal = document.getElementById('updateModal');
const currentVersionEl = document.getElementById('currentVersion');
const latestVersionEl = document.getElementById('latestVersion');
const changelogEl = document.getElementById('updateChangelog');
const updateButton = document.getElementById('updateButton');
currentVersionEl.textContent = `v${currentVersion}`;
latestVersionEl.textContent = `v${latestVersion}`;
const changelog = releaseData.body || 'Nincs elérhető változásnapló.';
changelogEl.innerHTML = parseMarkdown(changelog);
updateButton.href = releaseData.html_url;
modal.classList.add('active');
document.getElementById('dismissUpdate').addEventListener('click', closeUpdateModal);
}
function closeUpdateModal() {
const modal = document.getElementById('updateModal');
modal.classList.remove('active');
}

View File

@@ -152,8 +152,6 @@
</div>
</div>
<script src="../tools/sentry-browser.min.js"></script>
<script src="../tools/sentry.js"></script>
<script src="../tools/storageManager.js"></script>
<script src="../global/language.js"></script>
<script src="setup.js"></script>

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);

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() {
@@ -313,7 +313,12 @@
}
}
async function loadTestDetailsFromAPI(testId) {
async function loadAllTestDataFromAPI() {
const now = Date.now();
if (cachedTestData && (now - testDataTimestamp) < TEST_DATA_CACHE_DURATION) {
return cachedTestData;
}
try {
const timestamp = Date.now();
const apiUrl = `https://${window.location.hostname}/api/TanuloBejelentettSzamonkeresekApi/GetBejelentettSzamonkeresekGrid?sort=SzamonkeresDatuma-asc~Oraszam-asc&page=1&pageSize=1000&group=&filter=&data=%7B%22RegiSzamonkeresekElrejtese%22%3Afalse%7D&_=${timestamp}`;
@@ -327,15 +332,30 @@
});
if (!response.ok) {
throw new Error(
`Számonkérés API hiba: ${response.status}`,
);
throw new Error(`Számonkérés API hiba: ${response.status}`);
}
const data = await response.json();
const testData = data.Data || [];
cachedTestData = data.Data || [];
testDataTimestamp = now;
return cachedTestData;
} catch (error) {
console.error("Számonkérés adatok betöltési hiba:", error);
return [];
}
}
async function getTestTypeById(testId) {
const testData = await loadAllTestDataFromAPI();
const testDetail = testData.find(test => test.ID === testId.toString());
return testDetail ? testDetail.ErtekelesModNev : null;
}
async function loadTestDetailsFromAPI(testId) {
try {
const testData = await loadAllTestDataFromAPI();
const testDetail = testData.find(test => test.ID === testId.toString());
if (testDetail) {
return {
name: testDetail.SzamonkeresMegnevezes || 'Nincs megnevezés',
@@ -343,7 +363,7 @@
announceDate: testDetail.BejelentesDatuma ? new Date(testDetail.BejelentesDatuma).toLocaleDateString('hu-HU') : 'Nincs dátum'
};
}
return null;
} catch (error) {
console.error("Számonkérés adatok betöltési hiba:", error);
@@ -410,6 +430,7 @@
originalTeacher: "",
room: "",
day: dayIndex,
date: weekDates[dayIndex]?.fullDate || eventDate.toISOString().split('T')[0],
isSubstituted: false,
isCancelled: false,
hasHomework: false,
@@ -446,9 +467,9 @@
"Ismeretlen tantárgy";
if (startTimeStr && subject) {
const isCancelled = event.isElmaradt || event.Elmaradt || event.cancelled || event.isCancelled ||
const isCancelled = event.isElmaradt || event.Elmaradt || event.cancelled || event.isCancelled ||
event.oraType === 6 || (event.title && event.title.toLowerCase().includes('elmarad'));
const lesson = {
startTime: startTimeStr,
endTime: endTimeStr,
@@ -457,14 +478,15 @@
originalTeacher: event.helyettesitoId ? teacher : "",
room: room,
day: dayIndex,
date: weekDates[dayIndex]?.fullDate || eventDate.toISOString().split('T')[0],
isSubstituted: !!event.helyettesitoId,
isCancelled: isCancelled,
hasHomework: event.hasHaziFeladat || false,
testInfo: event.hasBejelentettSzamonkeres
? event.Tema || LanguageManager.t("timetable.test_indicator")
: "",
testId: event.hasBejelentettSzamonkeres && event.BejelentettSzamonkeresIdList && event.BejelentettSzamonkeresIdList.length > 0
? event.BejelentettSzamonkeresIdList[0]
testId: event.hasBejelentettSzamonkeres && event.BejelentettSzamonkeresIdList && event.BejelentettSzamonkeresIdList.length > 0
? event.BejelentettSzamonkeresIdList[0]
: null,
testDetails: "",
homeworkDetails: "",
@@ -497,6 +519,15 @@
}
}
const testTypeMap = {};
const lessonsWithTests = lessons.filter(l => l.testId);
if (lessonsWithTests.length > 0) {
await loadAllTestDataFromAPI();
for (const lesson of lessonsWithTests) {
testTypeMap[lesson.testId] = await getTestTypeById(lesson.testId);
}
}
const times = [...new Set(regularLessons.map((l) => l.startTime))].sort(
(a, b) => {
const timeA = helper.convertTimeToMinutes(a);
@@ -504,6 +535,26 @@
return timeA - timeB;
},
);
function getLessonTimeSlots(lesson, allTimes) {
const startMinutes = helper.convertTimeToMinutes(lesson.startTime);
const endMinutes = helper.convertTimeToMinutes(lesson.endTime);
const slots = [];
for (let i = 0; i < allTimes.length; i++) {
const slotMinutes = helper.convertTimeToMinutes(allTimes[i]);
if (slotMinutes >= startMinutes && slotMinutes < endMinutes) {
slots.push(i);
}
}
return slots.length > 0 ? slots : [allTimes.indexOf(lesson.startTime)];
}
const lessonSlotMap = new Map();
regularLessons.forEach(lesson => {
const slots = getLessonTimeSlots(lesson, times);
lessonSlotMap.set(lesson, slots);
});
const days = [
LanguageManager.t("timetable.monday"),
LanguageManager.t("timetable.tuesday"),
@@ -569,38 +620,84 @@
const dayLessons = regularLessons.filter(
(l) => l.startTime === time && l.day === dayIndex,
);
const continuingInSlot = regularLessons.filter(lesson => {
if (lesson.day !== dayIndex) return false;
const slots = lessonSlotMap.get(lesson);
return slots && slots.includes(timeIndex) && slots[0] !== timeIndex;
});
const hasMultiLesson = dayLessons.some(lesson => {
const slots = lessonSlotMap.get(lesson);
return slots && slots.length > 1;
});
const lastLessonTime = lastLessonTimes[dayIndex];
const isAfterLastLesson = lastLessonTime && helper.convertTimeToMinutes(time) > helper.convertTimeToMinutes(lastLessonTime);
if (dayLessons.length === 0 && isAfterLastLesson) {
if (dayLessons.length === 0 && continuingInSlot.length === 0 && isAfterLastLesson) {
return `<div class="lesson-slot"></div>`;
}
if (continuingInSlot.length > 0) {
return `
<div class="lesson-slot ${dayLessons.length === 0 ? 'empty-slot' : ''}">
${dayLessons.length === 0 ? '<div class="empty-lesson-placeholder"></div>' : ''}
${dayLessons
.map(
(lesson) => `
<div class="lesson-card ${lesson.isSubstituted ? "substituted" : ""}
<div class="lesson-slot has-continuation">
${continuingInSlot.map(lesson => {
const slots = lessonSlotMap.get(lesson);
const isLastSlot = slots[slots.length - 1] === timeIndex;
const lessonGroupId = `${lesson.subject}_${lesson.day}_${lesson.date}_${lesson.startTime}`;
return `
<div class="lesson-card ${lesson.isSubstituted ? "substituted" : ""}
${lesson.isCancelled ? "cancelled" : ""}
${lesson.hasHomework ? "has-homework" : ""}"
lesson-continuation ${isLastSlot ? 'continuation-end' : 'continuation-middle'}"
data-lesson='${JSON.stringify(lesson)}'
data-lesson-id='${lesson.lessonId || ""}'>
<div class="lesson-subject">${lesson.subject}</div>
<div class="lesson-teacher">${lesson.teacher}</div>
data-lesson-id='${lesson.lessonId || ""}'
data-lesson-group='${lessonGroupId}'>
<div class="lesson-bottom">
<div class="lesson-room">${lesson.room}</div>
<div class="lesson-time">${lesson.isCancelled ? LanguageManager.t("timetable.cancelled") : lesson.startTime}</div>
</div>
</div>
`}).join('')}
</div>
`;
}
return `
<div class="lesson-slot ${dayLessons.length === 0 ? 'empty-slot' : ''} ${hasMultiLesson ? 'has-multi-start' : ''}">
${dayLessons.length === 0 ? '<div class="empty-lesson-placeholder"></div>' : ''}
${dayLessons
.map(
(lesson) => {
const slots = lessonSlotMap.get(lesson);
const spansMultiple = slots && slots.length > 1;
const lessonGroupId = `${lesson.subject}_${lesson.day}_${lesson.date}_${lesson.startTime}`;
return `
<div class="lesson-card ${lesson.isSubstituted ? "substituted" : ""}
${lesson.isCancelled ? "cancelled" : ""}
${lesson.hasHomework ? "has-homework" : ""}
${spansMultiple ? "lesson-spans-multiple" : ""}"
data-lesson='${JSON.stringify(lesson)}'
data-lesson-id='${lesson.lessonId || ""}'
data-spans='${slots ? slots.length : 1}'
data-lesson-group='${lessonGroupId}'>
<div class="lesson-subject">${lesson.subject}</div>
<div class="lesson-teacher">${lesson.teacher}</div>
${!spansMultiple ? `<div class="lesson-bottom">
<div class="lesson-room">${lesson.room}</div>
<div class="lesson-time">${lesson.isCancelled ? LanguageManager.t("timetable.cancelled") : lesson.startTime}</div>
</div>` : ''}
${
(() => {
const lessonKey = `${lesson.subject}_${lesson.startTime}_${lesson.day}`;
const hasCustomHomework = customHomework[lessonKey] && customHomework[lessonKey].length > 0;
const hasCustomTests = customTests[lessonKey] && customTests[lessonKey].length > 0;
const lessonKey = `${lesson.subject}_${lesson.startTime}_${lesson.date}`;
const customHomeworkItems = customHomework[lessonKey] || [];
const customTestItems = customTests[lessonKey] || [];
const hasCustomHomework = customHomeworkItems.length > 0;
const hasCustomTests = customTestItems.length > 0;
const allCustomHomeworkCompleted = hasCustomHomework && customHomeworkItems.every(hw => hw.completed);
const allCustomTestsCompleted = hasCustomTests && customTestItems.every(test => test.completed);
const hasAnyIndicators = lesson.hasHomework || lesson.testInfo || hasCustomHomework || hasCustomTests;
return hasAnyIndicators ? `
<div class="lesson-indicators">
${
@@ -614,18 +711,40 @@
}
${
lesson.testInfo
? `
<span class="lesson-indicator test-indicator" title="${LanguageManager.t("timetable.test_indicator")}">
<img src="${chrome.runtime.getURL("icons/assigment.svg")}" alt="Teszt" style="width: 20px; height: 20px;">
? (() => {
const testType = lesson.testId ? testTypeMap[lesson.testId] : null;
const isKontaktOra = testType === "KONTAKT ÓRA";
const isProjektOra = testType === "PROJEKT ÓRA";
let indicatorClass = "test-indicator";
let iconPath = "icons/assigment.svg";
let titleText = LanguageManager.t("timetable.test_indicator");
let altText = "Teszt";
if (isKontaktOra) {
indicatorClass = "homework-indicator";
iconPath = "icons/contact.svg";
titleText = "Kontakt óra";
altText = "Kontakt óra";
} else if (isProjektOra) {
indicatorClass = "homework-indicator";
iconPath = "icons/project.svg";
titleText = "Projekt óra";
altText = "Projekt óra";
}
return `
<span class="lesson-indicator ${indicatorClass}" title="${titleText}">
<img src="${chrome.runtime.getURL(iconPath)}" alt="${altText}" style="width: 20px; height: 20px;">
</span>
`
`;
})()
: ""
}
${
hasCustomHomework
? `
<span class="lesson-indicator custom-homework-indicator" title="Saját házi feladat">
<img src="${chrome.runtime.getURL("icons/homework.svg")}" alt="Saját házi feladat" style="width: 20px; height: 20px; opacity: 0.7;">
<img src="${chrome.runtime.getURL(allCustomHomeworkCompleted ? "icons/pipa.svg" : "icons/homework.svg")}" alt="${allCustomHomeworkCompleted ? 'Megoldott saját házi feladat' : 'Saját házi feladat'}" style="width: 20px; height: 20px; opacity: 0.7;">
</span>
`
: ""
@@ -634,7 +753,7 @@
hasCustomTests
? `
<span class="lesson-indicator custom-test-indicator" title="Saját számonkérés">
<img src="${chrome.runtime.getURL("icons/assigment.svg")}" alt="Saját számonkérés" style="width: 20px; height: 20px; opacity: 0.7;">
<img src="${chrome.runtime.getURL(allCustomTestsCompleted ? "icons/pipa.svg" : "icons/assigment.svg")}" alt="${allCustomTestsCompleted ? 'Megoldott saját számonkérés' : 'Saját számonkérés'}" style="width: 20px; height: 20px; opacity: 0.7;">
</span>
`
: ""
@@ -644,8 +763,8 @@
})()
}
</div>
`,
)
`;
})
.join("")
}
</div>
@@ -1196,83 +1315,92 @@
}
if (lesson.testInfo) {
let testDetails = null;
if (lesson.testId) {
testDetails = await loadTestDetailsFromAPI(lesson.testId);
}
const isKontaktOra = testDetails && testDetails.type === "KONTAKT ÓRA";
const isProjektOra = testDetails && testDetails.type === "PROJEKT ÓRA";
const isSpecialType = isKontaktOra || isProjektOra;
const testSection = document.createElement('div');
testSection.className = 'modal-section test-section';
testSection.className = isSpecialType ? 'modal-section homework-section' : 'modal-section test-section';
const testH4 = document.createElement('h4');
const testIcon = document.createElement('img');
testIcon.src = chrome.runtime.getURL('icons/assigment.svg');
testIcon.alt = 'Teszt';
let iconPath = 'icons/assigment.svg';
let sectionTitle = LanguageManager.t('timetable.test_indicator');
let altText = 'Teszt';
if (isKontaktOra) {
iconPath = 'icons/contact.svg';
sectionTitle = 'Kontakt óra';
altText = 'Kontakt óra';
} else if (isProjektOra) {
iconPath = 'icons/project.svg';
sectionTitle = 'Projekt óra';
altText = 'Projekt óra';
}
testIcon.src = chrome.runtime.getURL(iconPath);
testIcon.alt = altText;
testIcon.style.width = '20px';
testIcon.style.height = '20px';
testH4.appendChild(testIcon);
testH4.appendChild(document.createTextNode(LanguageManager.t('timetable.test_indicator')));
testH4.appendChild(document.createTextNode(sectionTitle));
if (isSpecialType) {
testH4.style.color = 'var(--accent-accent)';
}
const testContent = document.createElement('div');
testContent.className = 'test-content';
if (lesson.testId) {
const loadingDiv = document.createElement('div');
loadingDiv.className = 'test-details-loading';
loadingDiv.textContent = 'Részletek betöltése...';
testContent.appendChild(loadingDiv);
if (testDetails) {
const detailsDiv = document.createElement('div');
detailsDiv.className = 'test-details';
loadTestDetailsFromAPI(lesson.testId).then(testDetails => {
loadingDiv.remove();
if (testDetails) {
const detailsDiv = document.createElement('div');
detailsDiv.className = 'test-details';
const nameP = document.createElement('p');
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 {
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';
@@ -1280,10 +1408,10 @@
customTestsTitle.style.color = 'var(--warning-accent)';
customTestsTitle.style.marginBottom = '0.5rem';
customTestsDiv.appendChild(customTestsTitle);
const customTestsList = document.createElement('div');
customTestsList.className = 'custom-tests-list-integrated';
customTestItems.forEach(test => {
const testItem = document.createElement('div');
testItem.className = `custom-test-item-integrated ${test.completed ? 'completed' : ''}`;
@@ -1295,7 +1423,7 @@
testItem.style.background = 'var(--background)';
testItem.style.borderRadius = '6px';
testItem.style.border = '1px solid var(--background-0)';
const testText = document.createElement('span');
testText.className = 'test-text-integrated';
testText.textContent = test.text;
@@ -1305,12 +1433,12 @@
testText.style.textDecoration = 'line-through';
testText.style.opacity = '0.6';
}
const testActions = document.createElement('div');
testActions.className = 'test-actions-integrated';
testActions.style.display = 'flex';
testActions.style.gap = '0.5rem';
const completeBtn = document.createElement('button');
completeBtn.className = 'test-complete-btn-integrated';
completeBtn.title = test.completed ? 'Megoldva - kattints a visszavonáshoz' : 'Megoldottként jelöl';
@@ -1322,7 +1450,7 @@
completeBtn.style.display = 'flex';
completeBtn.style.alignItems = 'center';
completeBtn.style.justifyContent = 'center';
const completeIcon = document.createElement('img');
completeIcon.src = chrome.runtime.getURL('icons/pipa.svg');
completeIcon.alt = 'Megoldva';
@@ -1335,7 +1463,7 @@
completeIcon.style.opacity = '0.5';
}
completeBtn.appendChild(completeIcon);
const deleteBtn = document.createElement('button');
deleteBtn.className = 'test-delete-btn-integrated';
deleteBtn.title = 'Törlés';
@@ -1347,7 +1475,7 @@
deleteBtn.style.display = 'flex';
deleteBtn.style.alignItems = 'center';
deleteBtn.style.justifyContent = 'center';
const deleteIcon = document.createElement('img');
deleteIcon.src = chrome.runtime.getURL('icons/delete.svg');
deleteIcon.alt = 'Törlés';
@@ -1355,7 +1483,7 @@
deleteIcon.style.height = '16px';
deleteIcon.style.opacity = '0.5';
deleteBtn.appendChild(deleteIcon);
completeBtn.addEventListener('click', async () => {
const newCompleted = await toggleCustomTestCompletion(lessonKey, test.id);
if (newCompleted) {
@@ -1372,30 +1500,30 @@
completeBtn.title = 'Megoldottként jelöl';
}
});
deleteBtn.addEventListener('click', async () => {
if (confirm('Biztosan törölni szeretnéd ezt a számonkérést?')) {
await removeCustomTest(lessonKey, test.id);
testItem.remove();
}
});
testActions.appendChild(completeBtn);
testActions.appendChild(deleteBtn);
testItem.appendChild(testText);
testItem.appendChild(testActions);
customTestsList.appendChild(testItem);
});
customTestsDiv.appendChild(customTestsList);
testContent.appendChild(customTestsDiv);
}
testSection.appendChild(testH4);
testSection.appendChild(testContent);
body.appendChild(testSection);
}
const lessonKey = getLessonKey(lesson);
const customHomework = await getCustomHomework();
@@ -1890,6 +2018,24 @@
const lessonData = JSON.parse(card.dataset.lesson);
await showLessonModal(lessonData);
});
card.addEventListener("mouseenter", () => {
const groupId = card.dataset.lessonGroup;
if (groupId) {
document.querySelectorAll(`[data-lesson-group="${groupId}"]`).forEach(relatedCard => {
relatedCard.classList.add('group-hover');
});
}
});
card.addEventListener("mouseleave", () => {
const groupId = card.dataset.lessonGroup;
if (groupId) {
document.querySelectorAll(`[data-lesson-group="${groupId}"]`).forEach(relatedCard => {
relatedCard.classList.remove('group-hover');
});
}
});
});
}

View File

@@ -1,24 +1,3 @@
const SENTRY_DSN = 'https://c7d88b71f550a276f973885a44b6536d@o4510511576055808.ingest.de.sentry.io/4510511935193168';
async function initSentry() {
try {
const result = await chrome.storage.sync.get('firka_errorReporting');
const enabled = result.firka_errorReporting !== false;
if (enabled) {
self.addEventListener('error', (event) => {
console.error('[Background Error]', event.error || event);
});
self.addEventListener('unhandledrejection', (event) => {
console.error('[Background Unhandled Rejection]', event.reason);
});
}
} catch (error) {
console.error('[Sentry] Nem sikerült inicializálni:', error);
}
}
chrome.runtime.onInstalled.addListener(async (details) => {
if (details.reason === 'install') {
const setupCompleted = await chrome.storage.sync.get('firka_setupCompleted');
@@ -29,8 +8,6 @@ chrome.runtime.onInstalled.addListener(async (details) => {
});
}
}
await initSentry();
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
@@ -57,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' });
@@ -102,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);
}
@@ -112,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_'));

File diff suppressed because one or more lines are too long

View File

@@ -1,127 +0,0 @@
(function() {
'use strict';
const SENTRY_DSN = 'https://c7d88b71f550a276f973885a44b6536d@o4510511576055808.ingest.de.sentry.io/4510511935193168';
let sentryInitialized = false;
async function isErrorReportingEnabled() {
try {
const result = await chrome.storage.sync.get('firka_errorReporting');
return result.firka_errorReporting !== false;
} catch (error) {
return true;
}
}
async function initSentry() {
if (sentryInitialized) {
return;
}
const enabled = await isErrorReportingEnabled();
if (!enabled) {
return;
}
setTimeout(() => {
configureSentry();
}, 100);
}
function configureSentry() {
try {
const SentrySDK = window.Sentry || (typeof Sentry !== 'undefined' ? Sentry : null);
if (!SentrySDK) {
setTimeout(configureSentry, 500);
return;
}
if (!SentrySDK.init) {
return;
}
const manifest = chrome.runtime.getManifest();
SentrySDK.init({
dsn: SENTRY_DSN,
release: `firka-extension@${manifest.version}`,
environment: 'production',
integrations: [],
beforeSend(event, hint) {
if (event.request) {
delete event.request.cookies;
delete event.request.headers;
}
return event;
},
});
sentryInitialized = true;
} catch (error) {
}
}
window.addEventListener('error', function(event) {
if (sentryInitialized) {
const SentrySDK = window.Sentry || (typeof Sentry !== 'undefined' ? Sentry : null);
if (SentrySDK && SentrySDK.captureException) {
SentrySDK.captureException(event.error || new Error(event.message));
} else {
console.warn('[Sentry] SDK not available for capturing');
}
} else {
console.warn('[Sentry] Not initialized yet, cannot capture error');
}
}, true);
window.addEventListener('unhandledrejection', function(event) {
if (sentryInitialized) {
const SentrySDK = window.Sentry || (typeof Sentry !== 'undefined' ? Sentry : null);
if (SentrySDK && SentrySDK.captureException) {
SentrySDK.captureException(event.reason);
}
}
}, true);
if (typeof chrome !== 'undefined' && chrome.storage) {
chrome.storage.onChanged.addListener(function(changes, namespace) {
if (namespace === 'sync' && changes.firka_errorReporting) {
const newValue = changes.firka_errorReporting.newValue;
if (newValue === false && sentryInitialized) {
if (typeof Sentry !== 'undefined' && Sentry.close) {
Sentry.close();
sentryInitialized = false;
}
} else if (newValue !== false && !sentryInitialized) {
initSentry();
}
}
});
}
window.FirkaSentry = {
init: initSentry,
isEnabled: isErrorReportingEnabled,
captureException: function(error) {
if (sentryInitialized) {
const SentrySDK = window.Sentry || (typeof Sentry !== 'undefined' ? Sentry : null);
if (SentrySDK && SentrySDK.captureException) {
SentrySDK.captureException(error);
}
}
},
captureMessage: function(message, level = 'info') {
if (sentryInitialized) {
const SentrySDK = window.Sentry || (typeof Sentry !== 'undefined' ? Sentry : null);
if (SentrySDK && SentrySDK.captureMessage) {
SentrySDK.captureMessage(message, level);
}
}
}
};
initSentry();
})();