Files
student-web-if-development-kit/groups/Tapops/js/utils/document-formatter.js
T

205 lines
6.8 KiB
JavaScript

export class DocumentFormatter {
static MONTHS_ID = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
static CATEGORY_RULES = [
{ key: 'skripsi', match: ['skripsi', 'tugas akhir', 'ta'] },
{ key: 'akademik', match: ['akademik', 'kalender', 'kaldik'] },
{ key: 'praktik', match: ['praktik', 'praktek', 'kerja praktik', 'kp'] },
];
static TAG_ABBREVIATIONS = new Set(['kp', 'sop', 'ta', 'krs', 'khs', 'sks', 'pkl', 'mbkm']);
static FILE_TYPE_RULES = [
{ extensions: ['pdf'], mimeTypes: ['application/pdf'], icon: 'fas fa-file-pdf', css: 'doc-type-pdf' },
{
extensions: ['doc', 'docx'],
mimeTypes: [
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
],
fallbackType: 'docx',
icon: 'fas fa-file-word',
css: 'doc-type-docx',
},
{
extensions: ['ppt', 'pptx'],
mimeTypes: [
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
],
fallbackType: 'pptx',
icon: 'fas fa-file-powerpoint',
css: 'doc-type-pptx',
},
{
extensions: ['xls', 'xlsx'],
mimeTypes: [
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
],
fallbackType: 'xlsx',
icon: 'fas fa-file-excel',
css: 'doc-type-xlsx',
},
{ extensions: ['csv'], mimeTypes: ['text/csv'], icon: 'fas fa-file-csv', css: 'doc-type-xlsx' },
{ extensions: ['txt', 'rtf', 'md'], mimeTypes: ['text/plain', 'application/rtf'], icon: 'fas fa-file-alt', css: 'doc-type-txt' },
{
extensions: ['zip', 'rar', '7z', 'tar', 'gz'],
mimeTypes: ['application/zip', 'application/x-rar-compressed', 'application/x-7z-compressed', 'application/gzip'],
icon: 'fas fa-file-archive',
css: 'doc-type-archive',
},
{
extensions: ['jpg', 'jpeg', 'png', 'gif', 'svg', 'webp'],
mimePrefix: 'image/',
icon: 'fas fa-file-image',
css: 'doc-type-image',
},
];
static formatDate(iso) {
if (!iso) return '';
const date = new Date(iso);
if (Number.isNaN(date.getTime())) return '';
const day = String(date.getDate()).padStart(2, '0');
const month = this.MONTHS_ID[date.getMonth()];
return `${day} ${month} ${date.getFullYear()}`;
}
static categoryFromTags(tags) {
if (!tags || tags.length === 0) return 'umum';
const lower = tags.map(tag => tag.toLowerCase());
for (const rule of this.CATEGORY_RULES) {
if (rule.match.some(match => lower.includes(match))) return rule.key;
}
return 'umum';
}
static fileTypeFromName(name) {
return this.fileInfoFromName(name).type;
}
static fileInfoFromName(name) {
const extension = this.extensionFromName(name);
const rule = this.FILE_TYPE_RULES.find(item => item.extensions.includes(extension));
if (!rule) {
return {
type: extension || 'file',
icon: 'fas fa-file',
css: 'doc-type-file',
};
}
return {
type: extension,
icon: rule.icon,
css: rule.css,
};
}
static fileInfoFromItem(item) {
const file = item?.file && typeof item.file === 'object' ? item.file : {};
const nameCandidates = [
file.filename_download,
file.filename_disk,
item?.nama_berkas,
];
for (const name of nameCandidates) {
const info = this.fileInfoFromName(name);
if (info.type !== 'file') return info;
}
return this.fileInfoFromMime(file.type);
}
static fileInfoFromMime(type) {
const mime = String(type || '').trim().toLowerCase();
if (!mime) return this.fileInfoFromName('');
const rule = this.FILE_TYPE_RULES.find(item => (
item.mimeTypes?.includes(mime) || (item.mimePrefix && mime.startsWith(item.mimePrefix))
));
if (!rule) return this.fileInfoFromName('');
return {
type: rule.fallbackType || rule.extensions[0],
icon: rule.icon,
css: rule.css,
};
}
static extensionFromName(name) {
const normalized = String(name || '').trim().toLowerCase();
const lastSegment = normalized.split(/[\\/]/).pop() || '';
const extensionStart = lastSegment.lastIndexOf('.');
if (extensionStart <= 0 || extensionStart === lastSegment.length - 1) return '';
return lastSegment.slice(extensionStart + 1);
}
static downloadFilename(name, type) {
const ext = `.${type}`;
if (!name) return `dokumen${ext}`;
return name.toLowerCase().endsWith(ext.toLowerCase()) ? name : `${name}${ext}`;
}
static downloadFilenameFromItem(item) {
const file = item?.file && typeof item.file === 'object' ? item.file : {};
const originalFilename = file.filename_download || file.filename_disk;
if (originalFilename) return originalFilename;
const fileType = this.fileInfoFromItem(item).type;
return this.downloadFilename(item?.nama_berkas || 'dokumen', fileType);
}
static escapeHtml(str) {
if (str == null) return '';
return String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#39;');
}
static escapeAttr(str) {
return this.escapeHtml(str);
}
static formatTag(tag) {
const value = String(tag).trim().replace(/\s+/g, ' ');
if (!value) return value;
return value.split(' ').map(word => {
const lower = word.toLowerCase();
if (this.TAG_ABBREVIATIONS.has(lower) || /^[A-Z0-9]{2,}$/.test(word)) {
return lower.toUpperCase();
}
return lower.charAt(0).toUpperCase() + lower.slice(1);
}).join(' ');
}
static parseTags(raw) {
if (!raw) return [];
const str = String(raw).trim();
if (!str) return [];
if (str.startsWith('[')) {
try {
const arr = JSON.parse(str);
if (Array.isArray(arr)) {
return arr.map(tag => String(tag).trim()).filter(Boolean);
}
} catch {
// Fall through to comma-separated parsing.
}
}
return str.split(',').map(tag => tag.trim()).filter(Boolean);
}
}