forked from izu/student-web-if-development-kit
205 lines
6.8 KiB
JavaScript
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, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|