ci: use Gitea API for releases #2

Closed
adrianbonpin wants to merge 56 commits from feat/ismis-helper into prod
3 changed files with 339 additions and 0 deletions
Showing only changes of commit 78570c4088 - Show all commits
+64
View File
@@ -0,0 +1,64 @@
* { box-sizing: border-box; }
body {
width: 360px;
margin: 0;
font-family: system-ui, sans-serif;
font-size: 13px;
color: #222;
}
.top {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: #006699;
color: #fff;
}
.top h1 { margin: 0; font-size: 16px; }
.master { display: flex; align-items: center; gap: 6px; }
.switch { position: relative; display: inline-block; width: 40px; height: 22px; }
.switch input { display: none; }
.slider {
position: absolute; cursor: pointer; inset: 0;
background: #ccc; border-radius: 22px; transition: 0.2s;
}
.slider::before {
content: ""; position: absolute; height: 18px; width: 18px;
left: 2px; top: 2px; background: #fff; border-radius: 50%; transition: 0.2s;
}
.switch input:checked + .slider { background: #4caf50; }
.switch input:checked + .slider::before { transform: translateX(18px); }
.tabs { display: flex; border-bottom: 1px solid #ddd; }
.tabs button {
flex: 1; padding: 8px; border: none; background: #f5f5f5; cursor: pointer;
}
.tabs button.active { background: #fff; font-weight: bold; border-bottom: 2px solid #006699; }
.tab { padding: 12px; }
.tab h2 { margin: 0 0 8px; font-size: 14px; }
.tab h3 { margin: 12px 0 6px; font-size: 12px; color: #555; }
.profile-list { list-style: none; margin: 0 0 8px; padding: 0; }
.profile-list li {
display: flex; align-items: center; gap: 6px;
padding: 6px; border: 1px solid #ddd; border-radius: 4px; margin-bottom: 6px;
}
.profile-list .name { flex: 1; }
.profile-list .user { color: #777; font-size: 11px; }
.btn {
padding: 6px 10px; border: 1px solid #ccc; border-radius: 4px;
background: #fff; cursor: pointer;
}
.btn.primary { background: #006699; color: #fff; border-color: #006699; }
.btn-row { display: flex; gap: 6px; margin-top: 6px; }
.form input {
width: 100%; padding: 6px; margin-bottom: 6px;
border: 1px solid #ccc; border-radius: 4px;
}
.form .row { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.course-row {
border: 1px solid #ddd; border-radius: 4px; padding: 6px; margin-bottom: 6px;
}
.course-row .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.course-row input { margin-bottom: 4px; }
.status { margin-top: 8px; font-size: 12px; }
.status.error { color: #c00; }
.status.ok { color: #080; }
+63
View File
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="popup.css">
</head>
<body>
<header class="top">
<h1>ISMIS Helper</h1>
<div class="master">
<label class="switch">
<input type="checkbox" id="masterToggle">
<span class="slider"></span>
</label>
<span id="masterLabel">Enabled</span>
</div>
</header>
<nav class="tabs">
<button data-tab="login" class="active">Auto-Login</button>
<button data-tab="studyload">Study Load</button>
</nav>
<section id="tab-login" class="tab">
<h2>Profiles</h2>
<ul id="profileList" class="profile-list"></ul>
<button id="addProfile" class="btn">+ Add Profile</button>
<div id="profileForm" class="form" hidden>
<input id="pName" placeholder="Profile name">
<input id="pUsername" placeholder="Username">
<input id="pPassword" type="password" placeholder="Password">
<label class="row"><input type="checkbox" id="pEnabled"> Enabled</label>
<div class="btn-row">
<button id="saveProfile" class="btn primary">Save</button>
<button id="cancelProfile" class="btn">Cancel</button>
</div>
</div>
</section>
<section id="tab-studyload" class="tab" hidden>
<h2>Study Load PDF</h2>
<button id="loadFromPortal" class="btn primary">Load from portal</button>
<div id="studyLoadForm" class="form" hidden>
<h3>Header</h3>
<input id="hFullName" placeholder="Full name">
<input id="hStudentId" placeholder="Student ID">
<input id="hProgram" placeholder="Program">
<input id="hYearLevel" placeholder="Year level">
<input id="hSemester" placeholder="Semester">
<input id="hDateEnrolled" placeholder="Date enrolled">
<input id="hDateRange" placeholder="Date range (e.g. AUGUST - JANUARY)">
<h3>Courses</h3>
<div id="coursesEditor"></div>
<button id="addCourse" class="btn">+ Add course</button>
<input id="totalUnits" placeholder="Total units">
<button id="generatePdf" class="btn primary">Generate PDF</button>
</div>
<div id="status" class="status"></div>
</section>
<script src="popup.js"></script>
</body>
</html>
+212
View File
@@ -0,0 +1,212 @@
'use strict';
const $ = (sel) => document.querySelector(sel);
const MASTER_KEY = 'enabled';
const PROFILES_KEY = 'profiles';
let editingProfileId = null;
// ---- Master toggle ----
async function loadMaster() {
const { enabled } = await browser.storage.local.get(MASTER_KEY);
$('#masterToggle').checked = enabled !== false;
$('#masterLabel').textContent = enabled === false ? 'Disabled' : 'Enabled';
}
$('#masterToggle').addEventListener('change', async (e) => {
await browser.storage.local.set({ [MASTER_KEY]: e.target.checked });
$('#masterLabel').textContent = e.target.checked ? 'Enabled' : 'Disabled';
});
// ---- Profiles ----
async function getProfiles() {
const { profiles } = await browser.storage.local.get(PROFILES_KEY);
return profiles || [];
}
async function saveProfiles(profiles) {
await browser.storage.local.set({ [PROFILES_KEY]: profiles });
}
function renderProfiles(profiles) {
const list = $('#profileList');
list.innerHTML = '';
for (const p of profiles) {
const li = document.createElement('li');
const name = document.createElement('div');
name.className = 'name';
name.innerHTML = '<strong>' + escapeHtml(p.name) + '</strong><br><span class="user">' + escapeHtml(p.username) + '</span>';
const toggle = document.createElement('input');
toggle.type = 'checkbox';
toggle.checked = !!p.enabled;
toggle.title = 'Enable/disable';
toggle.addEventListener('change', async () => {
p.enabled = toggle.checked;
await saveProfiles(profiles);
});
const edit = document.createElement('button');
edit.className = 'btn';
edit.textContent = 'Edit';
edit.addEventListener('click', () => openProfileForm(p));
const del = document.createElement('button');
del.className = 'btn';
del.textContent = 'Delete';
del.addEventListener('click', async () => {
await saveProfiles(profiles.filter((x) => x.id !== p.id));
renderProfiles(await getProfiles());
});
li.append(toggle, name, edit, del);
list.appendChild(li);
}
}
function escapeHtml(s) {
return String(s).replace(/[&<>"']/g, (c) => ({
'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'
}[c]));
}
function openProfileForm(p) {
editingProfileId = p ? p.id : null;
$('#pName').value = p ? p.name : '';
$('#pUsername').value = p ? p.username : '';
$('#pPassword').value = p ? p.password : '';
$('#pEnabled').checked = p ? !!p.enabled : true;
$('#profileForm').hidden = false;
}
$('#addProfile').addEventListener('click', () => openProfileForm(null));
$('#cancelProfile').addEventListener('click', () => { $('#profileForm').hidden = true; });
$('#saveProfile').addEventListener('click', async () => {
const profiles = await getProfiles();
const data = {
id: editingProfileId || Date.now().toString(),
name: $('#pName').value.trim(),
username: $('#pUsername').value.trim(),
password: $('#pPassword').value,
enabled: $('#pEnabled').checked
};
if (!data.name || !data.username) return;
if (editingProfileId) {
const i = profiles.findIndex((x) => x.id === editingProfileId);
if (i >= 0) profiles[i] = data;
} else {
profiles.push(data);
}
await saveProfiles(profiles);
$('#profileForm').hidden = true;
renderProfiles(profiles);
});
// ---- Tabs ----
document.querySelectorAll('.tabs button').forEach((btn) => {
btn.addEventListener('click', () => {
document.querySelectorAll('.tabs button').forEach((b) => b.classList.remove('active'));
btn.classList.add('active');
$('#tab-login').hidden = btn.dataset.tab !== 'login';
$('#tab-studyload').hidden = btn.dataset.tab !== 'studyload';
});
});
// ---- Study Load ----
function setStatus(msg, cls) {
const el = $('#status');
el.textContent = msg;
el.className = 'status ' + (cls || '');
}
function renderCourses(courses) {
const box = $('#coursesEditor');
box.innerHTML = '';
courses.forEach((c, i) => {
const row = document.createElement('div');
row.className = 'course-row';
row.innerHTML =
'<div class="grid">' +
'<input data-i="' + i + '" data-f="group" placeholder="Group" value="' + escapeHtml(c.group || '') + '">' +
'<input data-i="' + i + '" data-f="courseCode" placeholder="Course code" value="' + escapeHtml(c.courseCode || '') + '">' +
'<input data-i="' + i + '" data-f="description" placeholder="Description" value="' + escapeHtml(c.description || '') + '">' +
'<input data-i="' + i + '" data-f="units" placeholder="Units" value="' + escapeHtml(c.units || '') + '">' +
'<input data-i="' + i + '" data-f="teacher" placeholder="Teacher" value="' + escapeHtml(c.teacher || '') + '">' +
'<input data-i="' + i + '" data-f="status" placeholder="Status" value="' + escapeHtml(c.status || '') + '">' +
'</div>' +
'<input data-i="' + i + '" data-f="schedule" placeholder="Schedule" value="' + escapeHtml(c.schedule || '') + '">' +
'<button class="btn remove-course" data-i="' + i + '">Remove</button>';
box.appendChild(row);
});
box.querySelectorAll('input').forEach((inp) => {
inp.addEventListener('input', () => {
const i = +inp.dataset.i;
const f = inp.dataset.f;
studyLoadCourses[i][f] = inp.value;
});
});
box.querySelectorAll('.remove-course').forEach((btn) => {
btn.addEventListener('click', () => {
studyLoadCourses.splice(+btn.dataset.i, 1);
renderCourses(studyLoadCourses);
});
});
}
let studyLoadCourses = [];
$('#loadFromPortal').addEventListener('click', async () => {
setStatus('Loading from portal...');
try {
const res = await browser.runtime.sendMessage({ type: 'scrape-study-load' });
if (!res || !res.ok) throw new Error((res && res.error) || 'Scrape failed');
const d = res.data;
$('#hFullName').value = d.header.fullName || '';
$('#hStudentId').value = d.header.studentId || '';
$('#hProgram').value = d.header.program || '';
$('#hYearLevel').value = d.header.yearLevel || '';
$('#hSemester').value = d.header.semester || '';
$('#hDateEnrolled').value = d.header.dateEnrolled || '';
$('#hDateRange').value = d.header.dateRange || '';
$('#totalUnits').value = d.totalUnits || '';
studyLoadCourses = d.courses || [];
renderCourses(studyLoadCourses);
$('#studyLoadForm').hidden = false;
setStatus('Loaded. Review and generate.', 'ok');
} catch (err) {
setStatus('Could not load from portal: ' + err.message + ' — enter manually below.', 'error');
studyLoadCourses = [];
renderCourses(studyLoadCourses);
$('#studyLoadForm').hidden = false;
}
});
$('#addCourse').addEventListener('click', () => {
studyLoadCourses.push({ group: '', courseCode: '', description: '', units: '', teacher: '', schedule: '', status: '' });
renderCourses(studyLoadCourses);
});
$('#generatePdf').addEventListener('click', async () => {
const data = {
header: {
fullName: $('#hFullName').value.trim(),
studentId: $('#hStudentId').value.trim(),
program: $('#hProgram').value.trim(),
yearLevel: $('#hYearLevel').value.trim(),
semester: $('#hSemester').value.trim(),
dateEnrolled: $('#hDateEnrolled').value.trim(),
dateRange: $('#hDateRange').value.trim()
},
courses: studyLoadCourses,
totalUnits: $('#totalUnits').value.trim()
};
if (!data.courses.length) { setStatus('Add at least one course.', 'error'); return; }
setStatus('Generating PDF...');
try {
const res = await browser.runtime.sendMessage({ type: 'generate-pdf', data });
if (!res || !res.ok) throw new Error((res && res.error) || 'Generation failed');
setStatus('PDF downloaded.', 'ok');
} catch (err) {
setStatus('Error: ' + err.message, 'error');
}
});
// ---- Init ----
(async function init() {
await loadMaster();
renderProfiles(await getProfiles());
})();