feat: drhider PDF→DOCX conversion (LibreOffice), collision dialog (v1.9)
Deploy contracts-flask / validate (push) Successful in 0s

This commit is contained in:
2026-07-07 16:14:00 +04:00
parent e128b2614a
commit 211f391c6c
4 changed files with 144 additions and 13 deletions
+59 -4
View File
@@ -84,7 +84,7 @@
<a href="https://contractor.pythonk8s.services.ngcloud.ru/">Сверка договоров</a>
<span class="sep">|</span>
<strong>DrHider</strong>
<span style="font-size:11px;color:var(--muted);">v1.8</span>
<span style="font-size:11px;color:var(--muted);">v1.9</span>
<button class="help-btn" onclick="openModal()" title="О сервисе">?</button>
</header>
@@ -123,7 +123,7 @@
<h3>✅ Форматы файлов</h3>
<ul>
<li><strong>.docx</strong> — полная замена с сохранением структуры документа. ⚠️ Форматирование (жирный, курсив) заменённых фрагментов может сброситься.</li>
<li><strong>.pdf</strong>текст извлекается и обфусцируется. Результат: <code>.txt</code>. Форматирование, таблицы и графика не сохраняются.</li>
<li><strong>.pdf</strong>конвертируется в .docx (через LibreOffice), затем обфусцируется с сохранением форматирования. При совпадении имён с существующим .docx добавляется суффикс <code>_из_pdf</code>.</li>
<li><strong>.doc</strong> — бинарный формат, <strong>не обфусцируется</strong>. Файл возвращается как есть. Конвертируйте в .docx перед загрузкой.</li>
<li><strong>.zip</strong> — автоматически распаковывается, все файлы внутри обрабатываются.</li>
</ul>
@@ -139,7 +139,7 @@
<strong>.doc не обрабатывается:</strong> старые Word-файлы возвращаются без изменений.
</div>
<div class="warn">
<strong>PDF → текст:</strong> таблицы, графика и форматирование теряются.
<strong>PDF → DOCX:</strong> конвертация через LibreOffice может незначительно изменить форматирование сложных PDF.
</div>
<div class="warn">
<strong>Сканы/изображения:</strong> текст на картинках не распознаётся (нет OCR).
@@ -211,6 +211,44 @@ function render() {
BTN.onclick = () => {
if (!files.length) return;
// Проверить коллизии PDF→DOCX
const collisions = [];
const names = files.map(f => f.name);
files.forEach(f => {
if (f.name.toLowerCase().endsWith('.pdf')) {
const docxName = f.name.slice(0, -4) + '.docx';
if (names.includes(docxName)) {
collisions.push({pdf: f.name, docx: docxName});
}
}
});
if (collisions.length) {
showCollisionModal(collisions);
return;
}
doSubmit();
};
function showCollisionModal(collisions) {
const list = collisions.map(c => `<div style="margin-bottom:4px">📄 <b>${esc(c.pdf)}</b> → <b>${esc(c.docx)}</b> (уже есть)</div>`).join('');
document.getElementById('collisionList').innerHTML = list;
document.getElementById('collisionOverlay').classList.add('show');
window._collisions = collisions;
}
function closeCollision(skipAll) {
document.getElementById('collisionOverlay').classList.remove('show');
if (skipAll) {
// Удалить PDF-файлы из списка
const pdfNames = window._collisions.map(c => c.pdf);
files = files.filter(f => !pdfNames.includes(f.name));
render();
}
// В любом случае отправляем (бэкенд сам переименует)
setTimeout(doSubmit, 100);
}
function doSubmit() {
BTN.disabled = true;
setStatus('progress', '⏳ Отправка...');
const xhr = new XMLHttpRequest();
@@ -248,7 +286,24 @@ function fmt(n) { return n>1e6 ? (n/1e6).toFixed(1)+' MB' : n>1e3 ? (n/1e3).toFi
// ── Модальное окно ─────────────────────────────────────────────
function openModal() { document.getElementById('modalOverlay').classList.add('show'); }
function closeModal() { document.getElementById('modalOverlay').classList.remove('show'); }
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeModal(); });
// ── Коллизия PDF→DOCX ──────────────────────────────────────────
document.addEventListener('keydown', e => { if (e.key === 'Escape') closeCollision(true); });
</script>
<!-- Модальное окно: коллизия PDF→DOCX -->
<div class="modal-overlay" id="collisionOverlay">
<div class="modal" style="max-width:480px">
<div class="modal-header">⚠️ Конфликт имён</div>
<div class="modal-body">
<p style="margin-bottom:10px">PDF-файлы конвертируются в DOCX. Имена совпадают:</p>
<div id="collisionList" style="margin-bottom:12px"></div>
<p style="font-size:12px;color:var(--muted)">«Перезаписать» — PDF-файл заменит DOCX.<br>«Пропустить» — PDF будут удалены из списка.</p>
</div>
<div style="display:flex;gap:8px;padding:12px 16px;border-top:1px solid var(--brand-gray)">
<button class="btn" onclick="closeCollision(true)" style="flex:1">Пропустить PDF</button>
<button class="btn btn-primary" onclick="closeCollision(false)" style="flex:1">Перезаписать</button>
</div>
</div>
</div>
</body>
</html>