загрузка при выборе файла: прогресс, таймер, блокировка input, v1.13
This commit is contained in:
+105
-114
@@ -32,7 +32,7 @@
|
|||||||
th { background: var(--brand-grey-light); text-transform: uppercase; padding: 6px 10px; border-right: 1px solid var(--brand-gray); text-align: left; font-weight: 600; font-size: 11px; color: var(--muted); }
|
th { background: var(--brand-grey-light); text-transform: uppercase; padding: 6px 10px; border-right: 1px solid var(--brand-gray); text-align: left; font-weight: 600; font-size: 11px; color: var(--muted); }
|
||||||
td { padding: 6px 10px; border-right: 1px solid var(--brand-gray); border-bottom: 1px solid var(--brand-gray); }
|
td { padding: 6px 10px; border-right: 1px solid var(--brand-gray); border-bottom: 1px solid var(--brand-gray); }
|
||||||
tr:hover td { background: rgba(243,244,246,.5); }
|
tr:hover td { background: rgba(243,244,246,.5); }
|
||||||
.name-cell { max-width: 360px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
.name-cell { max-width: 340px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
.zip-child .name-cell { padding-left: 28px; }
|
.zip-child .name-cell { padding-left: 28px; }
|
||||||
.zip-child .name-cell::before { content: "└ "; color: var(--muted); }
|
.zip-child .name-cell::before { content: "└ "; color: var(--muted); }
|
||||||
.num-cell { text-align: right; white-space: nowrap; }
|
.num-cell { text-align: right; white-space: nowrap; }
|
||||||
@@ -45,7 +45,6 @@
|
|||||||
.info-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px 4px; font-size: 14px; display: none; }
|
.info-btn { cursor: pointer; color: var(--muted); background: none; border: none; padding: 2px 4px; font-size: 14px; display: none; }
|
||||||
.info-btn:hover { color: var(--brand-primary); }
|
.info-btn:hover { color: var(--brand-primary); }
|
||||||
.info-btn.visible { display: inline; }
|
.info-btn.visible { display: inline; }
|
||||||
/* Modal */
|
|
||||||
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,.4); z-index: 100; justify-content: center; align-items: center; }
|
.modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,.4); z-index: 100; justify-content: center; align-items: center; }
|
||||||
.modal-overlay.open { display: flex; }
|
.modal-overlay.open { display: flex; }
|
||||||
.modal { background: var(--background); border-radius: 12px; border: 1px solid var(--brand-gray); box-shadow: 0 4px 24px rgba(0,0,0,.15); max-width: 520px; width: 90%; max-height: 80vh; overflow-y: auto; }
|
.modal { background: var(--background); border-radius: 12px; border: 1px solid var(--brand-gray); box-shadow: 0 4px 24px rgba(0,0,0,.15); max-width: 520px; width: 90%; max-height: 80vh; overflow-y: auto; }
|
||||||
@@ -60,7 +59,7 @@
|
|||||||
<body>
|
<body>
|
||||||
<div class="topbar">
|
<div class="topbar">
|
||||||
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
<img src="{{ url_for('static', filename='nubes-logo.svg') }}" alt="Nubes">
|
||||||
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.12</span></span>
|
<span class="title">Сверка договоров <span style="font-weight:400;color:var(--muted);font-size:12px;">v1.13</span></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
@@ -88,7 +87,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Модальное окно Инфо -->
|
|
||||||
<div class="modal-overlay" id="modalOverlay" onclick="closeModal(event)">
|
<div class="modal-overlay" id="modalOverlay" onclick="closeModal(event)">
|
||||||
<div class="modal" onclick="event.stopPropagation()">
|
<div class="modal" onclick="event.stopPropagation()">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@@ -108,6 +106,7 @@
|
|||||||
const fileTable = document.getElementById('fileTable');
|
const fileTable = document.getElementById('fileTable');
|
||||||
|
|
||||||
let fileQueue = [];
|
let fileQueue = [];
|
||||||
|
let contractId = null;
|
||||||
|
|
||||||
function formatSize(bytes) {
|
function formatSize(bytes) {
|
||||||
if (!bytes || bytes === 0) return '—';
|
if (!bytes || bytes === 0) return '—';
|
||||||
@@ -127,14 +126,13 @@
|
|||||||
fileTable.innerHTML = '<tr class="empty-row"><td colspan="5">Нет файлов — выберите .docx / .pdf / .zip</td></tr>';
|
fileTable.innerHTML = '<tr class="empty-row"><td colspan="5">Нет файлов — выберите .docx / .pdf / .zip</td></tr>';
|
||||||
parseBtn.disabled = true;
|
parseBtn.disabled = true;
|
||||||
} else {
|
} else {
|
||||||
parseBtn.disabled = false;
|
|
||||||
fileTable.innerHTML = fileQueue.map(function(f, i) {
|
fileTable.innerHTML = fileQueue.map(function(f, i) {
|
||||||
var cls = f.isZipChild ? 'zip-child' : '';
|
var cls = f.isZipChild ? 'zip-child' : '';
|
||||||
return '<tr class="' + cls + '" id="row_' + i + '">' +
|
return '<tr class="' + cls + '" id="row_' + i + '">' +
|
||||||
'<td class="name-cell">' + f.name + '</td>' +
|
'<td class="name-cell">' + f.name + '</td>' +
|
||||||
'<td style="font-size:12px;color:var(--muted);">' + formatDate(f.lastModified) + '</td>' +
|
'<td style="font-size:12px;color:var(--muted);">' + formatDate(f.lastModified) + '</td>' +
|
||||||
'<td class="num-cell" style="font-size:12px;color:var(--muted);">' + formatSize(f.size) + '</td>' +
|
'<td class="num-cell" style="font-size:12px;color:var(--muted);">' + formatSize(f.size) + '</td>' +
|
||||||
'<td class="status-cell"></td>' +
|
'<td class="status-cell">' + (f.status || '') + '</td>' +
|
||||||
'<td><button class="remove-btn" onclick="removeFile(' + i + ')" title="Удалить">×</button>' +
|
'<td><button class="remove-btn" onclick="removeFile(' + i + ')" title="Удалить">×</button>' +
|
||||||
'<button class="info-btn" id="info_' + i + '" onclick="showInfo(' + i + ')" title="Инфо">ℹ</button></td>' +
|
'<button class="info-btn" id="info_' + i + '" onclick="showInfo(' + i + ')" title="Инфо">ℹ</button></td>' +
|
||||||
'</tr>';
|
'</tr>';
|
||||||
@@ -145,94 +143,45 @@
|
|||||||
function removeFile(index) {
|
function removeFile(index) {
|
||||||
var f = fileQueue[index];
|
var f = fileQueue[index];
|
||||||
if (!f) return;
|
if (!f) return;
|
||||||
if (!f.isZipChild && f.file && (f.file.type === 'application/zip' || f.name.toLowerCase().endsWith('.zip'))) {
|
if (!f.isZipChild && (f.name||'').toLowerCase().endsWith('.zip')) {
|
||||||
for (var i = fileQueue.length - 1; i >= 0; i--) {
|
for (var i = fileQueue.length - 1; i >= 0; i--) {
|
||||||
if (fileQueue[i].zipName === f.name) fileQueue.splice(i, 1);
|
if (fileQueue[i].zipName === f.name) fileQueue.splice(i, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fileQueue.splice(index, 1);
|
fileQueue.splice(index, 1);
|
||||||
|
if (fileQueue.filter(function(x){return !x.isZipChild;}).length === 0) contractId = null;
|
||||||
renderTable();
|
renderTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
window.removeFile = removeFile;
|
window.removeFile = removeFile;
|
||||||
|
|
||||||
fileInput.addEventListener('change', async function() {
|
// ── Загрузка файла (XHR с прогрессом) ──────────────────────
|
||||||
var newFiles = Array.from(fileInput.files);
|
|
||||||
|
|
||||||
for (var i = 0; i < newFiles.length; i++) {
|
function uploadFile(file, cid) {
|
||||||
var f = newFiles[i];
|
|
||||||
if (fileQueue.find(function(q) { return q.name === f.name && q.size === f.size; })) continue;
|
|
||||||
|
|
||||||
fileQueue.push({
|
|
||||||
name: f.name,
|
|
||||||
lastModified: f.lastModified,
|
|
||||||
size: f.size,
|
|
||||||
file: f
|
|
||||||
});
|
|
||||||
|
|
||||||
if (f.type === 'application/zip' || f.name.toLowerCase().endsWith('.zip')) {
|
|
||||||
try {
|
|
||||||
var zip = await JSZip.loadAsync(f);
|
|
||||||
zip.forEach(function(relativePath, zipEntry) {
|
|
||||||
if (!zipEntry.dir) {
|
|
||||||
var ext = relativePath.toLowerCase().split('.').pop();
|
|
||||||
if (['docx','doc','pdf'].indexOf(ext) === -1) return;
|
|
||||||
var zsize = (zipEntry._data && zipEntry._data.uncompressedSize) ? zipEntry._data.uncompressedSize : 0;
|
|
||||||
fileQueue.push({
|
|
||||||
name: relativePath,
|
|
||||||
lastModified: zipEntry.date ? zipEntry.date.getTime() : 0,
|
|
||||||
size: zsize,
|
|
||||||
isZipChild: true,
|
|
||||||
zipName: f.name
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch(e) {
|
|
||||||
console.log('JSZip error:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fileInput.value = '';
|
|
||||||
renderTable();
|
|
||||||
});
|
|
||||||
|
|
||||||
// ── Загрузка по одному файлу ──────────────────────────────
|
|
||||||
|
|
||||||
function uploadOne(file, cid, row) {
|
|
||||||
return new Promise(function(resolve, reject) {
|
return new Promise(function(resolve, reject) {
|
||||||
var xhr = new XMLHttpRequest();
|
var xhr = new XMLHttpRequest();
|
||||||
var url = '/' + (cid ? '?cid=' + cid : '');
|
var url = '/' + (cid ? '?cid=' + cid : '');
|
||||||
xhr.open('POST', url);
|
xhr.open('POST', url);
|
||||||
|
xhr.timeout = 120000;
|
||||||
|
|
||||||
xhr.upload.onprogress = function(e) {
|
xhr.upload.onprogress = function(e) {
|
||||||
if (e.lengthComputable && row) {
|
if (e.lengthComputable) {
|
||||||
var pct = Math.round(e.loaded / e.total * 100);
|
var pct = Math.round(e.loaded / e.total * 100);
|
||||||
row.querySelector('.status-cell').innerHTML = '<span style="color:var(--muted);">↑ ' + pct + '%</span>';
|
resolve({_progress: true, pct: pct});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onload = function() {
|
xhr.onload = function() {
|
||||||
if (xhr.status === 200) {
|
if (xhr.status === 200) {
|
||||||
try {
|
try { resolve(JSON.parse(xhr.responseText)); }
|
||||||
resolve(JSON.parse(xhr.responseText));
|
catch(e) { reject(new Error('Bad JSON')); }
|
||||||
} catch(e) {
|
|
||||||
reject(new Error('Bad JSON'));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
reject(new Error('HTTP ' + xhr.status));
|
reject(new Error('HTTP ' + xhr.status));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = function() {
|
xhr.onerror = function() { reject(new Error('Сеть')); };
|
||||||
reject(new Error('Сеть'));
|
xhr.ontimeout = function() { reject(new Error('Таймаут')); };
|
||||||
};
|
|
||||||
|
|
||||||
xhr.ontimeout = function() {
|
|
||||||
reject(new Error('Таймаут'));
|
|
||||||
};
|
|
||||||
|
|
||||||
xhr.timeout = 60000;
|
|
||||||
|
|
||||||
var fd = new FormData();
|
var fd = new FormData();
|
||||||
fd.append('files', file);
|
fd.append('files', file);
|
||||||
@@ -240,50 +189,104 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Парсинг ────────────────────────────────────────────────
|
// ── Выбор файла → сразу загрузка ────────────────────────────
|
||||||
|
|
||||||
parseBtn.addEventListener('click', async function() {
|
fileInput.addEventListener('change', async function() {
|
||||||
if (fileQueue.length === 0) return;
|
var newFiles = Array.from(fileInput.files);
|
||||||
|
if (newFiles.length === 0) return;
|
||||||
var realFiles = fileQueue.filter(function(f) { return !f.isZipChild; });
|
|
||||||
if (realFiles.length === 0) return;
|
|
||||||
|
|
||||||
|
fileInput.disabled = true;
|
||||||
parseBtn.disabled = true;
|
parseBtn.disabled = true;
|
||||||
parseBtn.innerHTML = '<span style="display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;"></span> Загрузка...';
|
parseBtn.innerHTML = '<span style="display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;"></span> Загрузка...';
|
||||||
|
|
||||||
var contractId = null;
|
for (var i = 0; i < newFiles.length; i++) {
|
||||||
|
var f = newFiles[i];
|
||||||
|
if (fileQueue.find(function(q) { return q.name === f.name && q.size === f.size && !q.isZipChild; })) continue;
|
||||||
|
|
||||||
// Загружаем каждый файл отдельно
|
// Добавить строку с прогрессом
|
||||||
for (var i = 0; i < realFiles.length; i++) {
|
var entry = { name: f.name, lastModified: f.lastModified, size: f.size, status: '<span style="color:var(--muted);">↑ 0%</span>' };
|
||||||
var f = realFiles[i];
|
fileQueue.push(entry);
|
||||||
var row = findRowByName(f.name);
|
var rowIdx = fileQueue.length - 1;
|
||||||
|
renderTable();
|
||||||
|
var row = document.getElementById('row_' + rowIdx);
|
||||||
|
|
||||||
if (row) {
|
var startTime = Date.now();
|
||||||
row.querySelector('.status-cell').innerHTML = '<span style="color:var(--muted);">↑ 0%</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
// Загружаем
|
||||||
var resp = await uploadOne(f.file, contractId, row);
|
var lastPct = 0;
|
||||||
contractId = resp.contract_id;
|
var done = false;
|
||||||
|
|
||||||
if (row) {
|
uploadFile(f, contractId).then(
|
||||||
row.querySelector('.status-cell').innerHTML = '<span style="color:var(--green);">✓ загружен</span>';
|
function(res) {
|
||||||
|
if (done) return;
|
||||||
|
if (res._progress) {
|
||||||
|
var pct = res.pct;
|
||||||
|
if (pct !== lastPct) {
|
||||||
|
lastPct = pct;
|
||||||
|
fileQueue[rowIdx].status = '<span style="color:var(--muted);">↑ ' + pct + '%</span>';
|
||||||
|
renderTable();
|
||||||
|
}
|
||||||
|
return; // ждём следующий прогресс или завершение
|
||||||
|
}
|
||||||
|
// Завершено
|
||||||
|
done = true;
|
||||||
|
contractId = res.contract_id;
|
||||||
|
var elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
||||||
|
fileQueue[rowIdx].status = '<span class="status-ok">✓ ' + elapsed + 'с</span>';
|
||||||
|
fileQueue[rowIdx].uploaded = true;
|
||||||
|
renderTable();
|
||||||
|
|
||||||
|
// ZIP: показать содержимое
|
||||||
|
if (f.type === 'application/zip' || f.name.toLowerCase().endsWith('.zip')) {
|
||||||
|
showZipContents(f);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
function(err) {
|
||||||
|
if (done) return;
|
||||||
|
done = true;
|
||||||
|
fileQueue[rowIdx].status = '<span class="status-err">✗ ' + err.message + '</span>';
|
||||||
|
renderTable();
|
||||||
}
|
}
|
||||||
} catch(e) {
|
);
|
||||||
if (row) {
|
|
||||||
row.querySelector('.status-cell').innerHTML = '<span class="status-err">✗ ' + e.message + '</span>';
|
|
||||||
}
|
|
||||||
resetBtn();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!contractId) {
|
fileInput.value = '';
|
||||||
resetBtn();
|
fileInput.disabled = false;
|
||||||
return;
|
parseBtn.disabled = false;
|
||||||
}
|
parseBtn.innerHTML = '<i data-lucide="play" style="width:16px;height:16px;"></i> Парсинг';
|
||||||
|
lucide.createIcons();
|
||||||
|
});
|
||||||
|
|
||||||
// Фаза парсинга — SSE
|
async function showZipContents(zipFile) {
|
||||||
|
try {
|
||||||
|
var zip = await JSZip.loadAsync(zipFile);
|
||||||
|
zip.forEach(function(relativePath, zipEntry) {
|
||||||
|
if (!zipEntry.dir) {
|
||||||
|
var ext = relativePath.toLowerCase().split('.').pop();
|
||||||
|
if (['docx','doc','pdf'].indexOf(ext) === -1) return;
|
||||||
|
var zsize = (zipEntry._data && zipEntry._data.uncompressedSize) ? zipEntry._data.uncompressedSize : 0;
|
||||||
|
fileQueue.push({
|
||||||
|
name: relativePath,
|
||||||
|
lastModified: zipEntry.date ? zipEntry.date.getTime() : 0,
|
||||||
|
size: zsize,
|
||||||
|
status: '',
|
||||||
|
isZipChild: true,
|
||||||
|
zipName: zipFile.name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
renderTable();
|
||||||
|
} catch(e) {
|
||||||
|
console.log('JSZip error:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Парсинг (SSE) ────────────────────────────────────────────
|
||||||
|
|
||||||
|
parseBtn.addEventListener('click', function() {
|
||||||
|
if (!contractId) return;
|
||||||
|
|
||||||
|
parseBtn.disabled = true;
|
||||||
parseBtn.innerHTML = '<span style="display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;"></span> Парсинг...';
|
parseBtn.innerHTML = '<span style="display:inline-block;width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:spin .6s linear infinite;"></span> Парсинг...';
|
||||||
|
|
||||||
var es = new EventSource('/parse/' + contractId);
|
var es = new EventSource('/parse/' + contractId);
|
||||||
@@ -301,8 +304,7 @@
|
|||||||
if (d.type === 'file_start') {
|
if (d.type === 'file_start') {
|
||||||
var row = findRowByName(d.name);
|
var row = findRowByName(d.name);
|
||||||
if (row) {
|
if (row) {
|
||||||
row.querySelector('.status-cell').innerHTML = '<span class="proc-timer" data-start="' + Date.now() + '">⏳ 0с</span>';
|
row.querySelector('.status-cell').innerHTML = '<span class="proc-timer" data-start="' + Date.now() + '">⏳ 0.0с</span>';
|
||||||
// Обновить размер если был неизвестен
|
|
||||||
if (d.bytes > 0) {
|
if (d.bytes > 0) {
|
||||||
var sizeCell = row.querySelector('.num-cell');
|
var sizeCell = row.querySelector('.num-cell');
|
||||||
if (sizeCell && sizeCell.textContent.trim() === '—') {
|
if (sizeCell && sizeCell.textContent.trim() === '—') {
|
||||||
@@ -316,13 +318,10 @@
|
|||||||
var row = findRowByName(d.name);
|
var row = findRowByName(d.name);
|
||||||
if (row) {
|
if (row) {
|
||||||
row.querySelector('.status-cell').innerHTML = '<span class="status-ok">✓ ' + d.time_s + 'с</span>';
|
row.querySelector('.status-cell').innerHTML = '<span class="status-ok">✓ ' + d.time_s + 'с</span>';
|
||||||
// Сохранить детали в fileQueue и показать ℹ
|
|
||||||
var idx = findIndexByName(d.name);
|
var idx = findIndexByName(d.name);
|
||||||
if (idx >= 0) {
|
if (idx >= 0) { fileQueue[idx].parseInfo = d; }
|
||||||
fileQueue[idx].parseInfo = d;
|
var infoBtn = document.getElementById('info_' + (idx >= 0 ? idx : ''));
|
||||||
var infoBtn = document.getElementById('info_' + idx);
|
if (infoBtn) infoBtn.classList.add('visible');
|
||||||
if (infoBtn) infoBtn.classList.add('visible');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,8 +342,6 @@
|
|||||||
else if (d.type === 'summary') {
|
else if (d.type === 'summary') {
|
||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
es.close();
|
es.close();
|
||||||
|
|
||||||
// Идемпотентность: больше нельзя запустить
|
|
||||||
parseBtn.disabled = true;
|
parseBtn.disabled = true;
|
||||||
parseBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px;"></i> ✓ Готово';
|
parseBtn.innerHTML = '<i data-lucide="check" style="width:16px;height:16px;"></i> ✓ Готово';
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
@@ -363,14 +360,10 @@
|
|||||||
es.addEventListener('error', function() {
|
es.addEventListener('error', function() {
|
||||||
clearInterval(timerInterval);
|
clearInterval(timerInterval);
|
||||||
es.close();
|
es.close();
|
||||||
resetBtn();
|
|
||||||
});
|
|
||||||
|
|
||||||
function resetBtn() {
|
|
||||||
parseBtn.disabled = false;
|
parseBtn.disabled = false;
|
||||||
parseBtn.innerHTML = '<i data-lucide="play" style="width:16px;height:16px;"></i> Парсинг';
|
parseBtn.innerHTML = '<i data-lucide="play" style="width:16px;height:16px;"></i> Парсинг';
|
||||||
lucide.createIcons();
|
lucide.createIcons();
|
||||||
}
|
});
|
||||||
|
|
||||||
function findRowByName(name) {
|
function findRowByName(name) {
|
||||||
for (var i = 0; i < fileQueue.length; i++) {
|
for (var i = 0; i < fileQueue.length; i++) {
|
||||||
@@ -399,10 +392,8 @@
|
|||||||
document.getElementById('modalTitle').textContent = f.name;
|
document.getElementById('modalTitle').textContent = f.name;
|
||||||
|
|
||||||
var rows = [
|
var rows = [
|
||||||
['Тип', f.file ? f.file.type : (f.name.split('.').pop() || '—')],
|
|
||||||
['Размер', formatSize(f.size)],
|
['Размер', formatSize(f.size)],
|
||||||
['В архиве', f.zipName || '—'],
|
['В архиве', f.zipName || '—'],
|
||||||
['Статус', 'parsed'],
|
|
||||||
['Время парсинга', d.time_s + 'с'],
|
['Время парсинга', d.time_s + 'с'],
|
||||||
['Элементов всего', d.elements],
|
['Элементов всего', d.elements],
|
||||||
['Параграфов', d.paragraphs],
|
['Параграфов', d.paragraphs],
|
||||||
|
|||||||
Reference in New Issue
Block a user