ci: use Gitea API for releases #2
+2
-1
@@ -168,9 +168,10 @@
|
|||||||
const included = ISMIStools.filterStudyLoadCourses(courses);
|
const included = ISMIStools.filterStudyLoadCourses(courses);
|
||||||
if (!included.length) throw new Error('No enrolled courses found.');
|
if (!included.length) throw new Error('No enrolled courses found.');
|
||||||
const totalUnits = ISMIStools.sumUnits(included);
|
const totalUnits = ISMIStools.sumUnits(included);
|
||||||
|
const { truncateDescription } = await browser.storage.local.get('truncateDescription');
|
||||||
const res = await browser.runtime.sendMessage({
|
const res = await browser.runtime.sendMessage({
|
||||||
type: 'generate-pdf',
|
type: 'generate-pdf',
|
||||||
data: { header, courses: included, totalUnits }
|
data: { header, courses: included, totalUnits, truncateDescription: truncateDescription !== false }
|
||||||
});
|
});
|
||||||
if (!res || !res.ok) throw new Error((res && res.error) || 'PDF generation failed');
|
if (!res || !res.ok) throw new Error((res && res.error) || 'PDF generation failed');
|
||||||
showToast('PDF downloaded.');
|
showToast('PDF downloaded.');
|
||||||
|
|||||||
@@ -64,6 +64,22 @@
|
|||||||
return out.length ? out : [''];
|
return out.length ? out : [''];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Truncate a string to the longest prefix that fits within maxWidth at the
|
||||||
|
// given size, hard-cutting mid-word with no ellipsis. Mirrors the official
|
||||||
|
// PDF which cuts off course descriptions that exceed the column width.
|
||||||
|
function truncateToWidth(text, font, size, maxWidth) {
|
||||||
|
const s = String(text == null ? '' : text).trim();
|
||||||
|
if (!s) return '';
|
||||||
|
if (font.widthOfTextAtSize(s, size) <= maxWidth) return s;
|
||||||
|
let lo = 0, hi = s.length;
|
||||||
|
while (lo < hi) {
|
||||||
|
const mid = Math.ceil((lo + hi) / 2);
|
||||||
|
if (font.widthOfTextAtSize(s.slice(0, mid), size) <= maxWidth) lo = mid;
|
||||||
|
else hi = mid - 1;
|
||||||
|
}
|
||||||
|
return s.slice(0, lo);
|
||||||
|
}
|
||||||
|
|
||||||
// Compact a degree name for display:
|
// Compact a degree name for display:
|
||||||
// "BACHELOR OF SCIENCE IN INFORMATION TECHNOLOGY" -> "BS IT".
|
// "BACHELOR OF SCIENCE IN INFORMATION TECHNOLOGY" -> "BS IT".
|
||||||
// Multi-word fields become initials; single-word fields stay title-cased.
|
// Multi-word fields become initials; single-word fields stay title-cased.
|
||||||
@@ -177,6 +193,9 @@
|
|||||||
const { PDFDocument, StandardFonts, rgb } = PDFLib;
|
const { PDFDocument, StandardFonts, rgb } = PDFLib;
|
||||||
const header = data.header || {};
|
const header = data.header || {};
|
||||||
const courses = data.courses || [];
|
const courses = data.courses || [];
|
||||||
|
// Truncate long course descriptions to a single line matching the official
|
||||||
|
// PDF (default ON). Disabling allows descriptions to wrap.
|
||||||
|
const truncateDescription = data.truncateDescription !== false;
|
||||||
|
|
||||||
const doc = await PDFDocument.create();
|
const doc = await PDFDocument.create();
|
||||||
let page = doc.addPage([612, 792]); // Letter
|
let page = doc.addPage([612, 792]); // Letter
|
||||||
@@ -278,7 +297,12 @@
|
|||||||
return cols.map((c) => {
|
return cols.map((c) => {
|
||||||
let lines;
|
let lines;
|
||||||
if (c.key === 'description') {
|
if (c.key === 'description') {
|
||||||
lines = wrapLines(cells.description, font, cellSize, c.w - 6);
|
if (truncateDescription) {
|
||||||
|
// Single line, hard-truncated to fit the column (official PDF style).
|
||||||
|
lines = [truncateToWidth(cells.description, font, cellSize, c.w - 3)];
|
||||||
|
} else {
|
||||||
|
lines = wrapLines(cells.description, font, cellSize, c.w - 6);
|
||||||
|
}
|
||||||
const sched = String(cells.schedule || '');
|
const sched = String(cells.schedule || '');
|
||||||
if (sched) lines.push(...wrapLines(sched, font, cellSize, c.w - 6));
|
if (sched) lines.push(...wrapLines(sched, font, cellSize, c.w - 6));
|
||||||
} else {
|
} else {
|
||||||
@@ -408,5 +432,5 @@
|
|||||||
return doc.save(); // returns Promise<Uint8Array>
|
return doc.save(); // returns Promise<Uint8Array>
|
||||||
}
|
}
|
||||||
|
|
||||||
return { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName };
|
return { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName, truncateToWidth };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -32,6 +32,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="tab">
|
||||||
|
<h2>PDF Settings</h2>
|
||||||
|
<label class="row"><input type="checkbox" id="truncateToggle"> Truncate long course descriptions (official PDF style)</label>
|
||||||
|
</section>
|
||||||
|
|
||||||
<script src="popup.js"></script>
|
<script src="popup.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -18,6 +18,15 @@ $('#masterToggle').addEventListener('change', async (e) => {
|
|||||||
$('#masterLabel').textContent = e.target.checked ? 'Enabled' : 'Disabled';
|
$('#masterLabel').textContent = e.target.checked ? 'Enabled' : 'Disabled';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---- PDF settings: truncate long course descriptions (default ON) ----
|
||||||
|
async function loadTruncate() {
|
||||||
|
const { truncateDescription } = await browser.storage.local.get('truncateDescription');
|
||||||
|
$('#truncateToggle').checked = truncateDescription !== false;
|
||||||
|
}
|
||||||
|
$('#truncateToggle').addEventListener('change', async (e) => {
|
||||||
|
await browser.storage.local.set({ truncateDescription: e.target.checked });
|
||||||
|
});
|
||||||
|
|
||||||
// ---- Profiles ----
|
// ---- Profiles ----
|
||||||
async function getProfiles() {
|
async function getProfiles() {
|
||||||
const { profiles } = await browser.storage.local.get(PROFILES_KEY);
|
const { profiles } = await browser.storage.local.get(PROFILES_KEY);
|
||||||
@@ -104,5 +113,6 @@ $('#saveProfile').addEventListener('click', async () => {
|
|||||||
// ---- Init ----
|
// ---- Init ----
|
||||||
(async function init() {
|
(async function init() {
|
||||||
await loadMaster();
|
await loadMaster();
|
||||||
|
await loadTruncate();
|
||||||
renderProfiles(await getProfiles());
|
renderProfiles(await getProfiles());
|
||||||
})();
|
})();
|
||||||
|
|||||||
+14
-1
@@ -5,7 +5,7 @@ const os = require('node:os');
|
|||||||
const path = require('node:path');
|
const path = require('node:path');
|
||||||
const { execFileSync } = require('node:child_process');
|
const { execFileSync } = require('node:child_process');
|
||||||
global.PDFLib = require('pdf-lib');
|
global.PDFLib = require('pdf-lib');
|
||||||
const { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName } = require('../pdf.js');
|
const { buildStudyLoadPdf, compactProgram, deriveDateRange, formatSchedule, formatFullName, truncateToWidth } = require('../pdf.js');
|
||||||
|
|
||||||
const sampleData = {
|
const sampleData = {
|
||||||
header: {
|
header: {
|
||||||
@@ -88,3 +88,16 @@ test('deriveDateRange maps semesters', () => {
|
|||||||
assert.strictEqual(deriveDateRange('Summer 2026'), 'JUNE - JULY');
|
assert.strictEqual(deriveDateRange('Summer 2026'), 'JUNE - JULY');
|
||||||
assert.strictEqual(deriveDateRange(''), '');
|
assert.strictEqual(deriveDateRange(''), '');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('truncateToWidth hard-cuts text to fit within maxWidth', async () => {
|
||||||
|
const doc = await global.PDFLib.PDFDocument.create();
|
||||||
|
const font = doc.embedStandardFont(global.PDFLib.StandardFonts.Helvetica);
|
||||||
|
const longText = 'APPLICATION DEVELOPMENT AND EMERGING TECHNOLOGIES';
|
||||||
|
const tr = truncateToWidth(longText, font, 8, 100);
|
||||||
|
assert.ok(tr.length < longText.length, 'long text should be cut down');
|
||||||
|
assert.ok(longText.startsWith(tr), 'result must be a prefix of the input');
|
||||||
|
assert.ok(font.widthOfTextAtSize(tr, 8) <= 100, 'truncated line must fit maxWidth');
|
||||||
|
const short = 'DATA STRUCTURES';
|
||||||
|
assert.strictEqual(truncateToWidth(short, font, 8, 200), short);
|
||||||
|
assert.strictEqual(truncateToWidth('', font, 8, 200), '');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user