v0.0.68: кнопка «Выбрать папку» — рекурсивный выбор папки с подпапками (webkitdirectory), относительный путь сохраняется
Deploy drhider / validate (push) Canceled after 0s

This commit is contained in:
“Naeel”
2026-08-24 16:47:48 +03:00
parent cfe6b9e2bf
commit f45ecb603d
4 changed files with 67 additions and 1 deletions
+1
View File
@@ -104,6 +104,7 @@
- **2026-08-24-interrupt-eta-design.md** — дизайн: TTL-фикс + прерывание с сохранением + оценка времени + UI.
- **2026-08-24-interrupt-eta-implemented.md** — v0.0.63: реализовано прерывание с сохранением + ETA + UI-таблица.
- **2026-08-24-time-tickers-estimates.md** — v0.0.640.0.65: тикер текущего файла на всех этапах, мгновенные оценки времени по файлам и суммарно.
- **2026-08-24-folder-select-implemented.md** — v0.0.68: кнопка «Выбрать папку» (webkitdirectory, рекурсивно, относительный путь).
- **2026-08-24-ui-locks-session-freeze.md** — v0.0.66: блокировки UI (выбор/удаление) на время работы, заморозка сессии + кнопка «Новая сессия».
## upload/ — загрузка файлов
@@ -0,0 +1,18 @@
# v0.0.68 — Кнопка «Выбрать папку» (webkitdirectory, рекурсивно) (2026-08-24)
_ux-frontend. По плану `plans/2026-08-24-folder-select-plan.md`. Код: `site/templates/index.html`._
## Что сделано
- Кнопка «📁 Выбрать папку» (`#folderBtn`) + скрытый `<input type="file" id="folderInput" webkitdirectory multiple>`.
- Обработчик `change` `#folderInput`:
- `if (busy) return` — во время загрузки/обработки выбор папки заблокирован.
- Берёт `webkitRelativePath` (`TopFolder/Подпапка/file.pdf`), **отбрасывает верхнюю папку**`rel`.
- `.zip` → раскрывает `listZipFiles`, префикс пути папки к именам; при ошибке — zip как есть.
- Документы (`.pdf .doc .docx .txt .md`) → `addFileWithDedup(new File([...], rel))`.
- Прочее — пропускается. Индикатор «Разбираю папку…», после — «✅ Добавлено N файлов».
- Дедуп/лимиты работают штатно (имя = относительный путь → подпапки не сливаются).
- Поддержка: Chrome/Edge — полно, Firefox — частично, Safari — нет (best-effort).
## Проверка
- `node --check` — OK; `py_compile` — OK.
- Версия 0.0.67 → 0.0.68.
+1 -1
View File
@@ -21,7 +21,7 @@ if _sys_path_root not in sys.path:
sys.path.insert(0, _sys_path_root)
# Версия приложения (меняется при изменениях)
VERSION = "0.0.67"
VERSION = "0.0.68"
def setup_logging():
+47
View File
@@ -144,6 +144,8 @@
</p>
<div class="file-input-wrap">
<input type="file" id="fileInput" multiple accept=".doc,.docx,.pdf,.txt,.zip">
<button type="button" class="btn" id="folderBtn" onclick="document.getElementById('folderInput').click()">📁 Выбрать папку</button>
<input type="file" id="folderInput" webkitdirectory multiple style="display:none">
</div>
<div class="table-wrap">
<table>
@@ -200,6 +202,8 @@
</div>
<script>
const fi = document.getElementById('fileInput');
const folderInput = document.getElementById('folderInput');
const DOC_EXTS = ['.pdf', '.doc', '.docx', '.txt', '.md']; // документы (из папки/архивов)
const fl = document.getElementById('fileList');
const fc = document.getElementById('fileCount');
const ub = document.getElementById('uploadBtn');
@@ -572,6 +576,49 @@ fi.addEventListener('change', async () => {
rr();
});
// ═══ Выбор целой папки (webkitdirectory): рекурсивно, относительный путь сохраняется ═══
folderInput.addEventListener('change', async () => {
if (busy) return; // во время загрузки/обработки менять список нельзя
const incoming = Array.from(folderInput.files);
if (!incoming.length) return;
document.body.style.cursor = 'wait';
st.className = 'status progress';
st.textContent = 'Разбираю папку…';
let added = 0;
try {
for (const f of incoming) {
// webkitRelativePath: "TopFolder/Подпапка/file.pdf" — отбрасываем верхнюю папку
const parts = (f.webkitRelativePath || f.name).split('/');
const rel = parts.slice(1).join('/') || f.name;
const low = rel.toLowerCase();
const slashIdx = rel.lastIndexOf('/');
const relDir = slashIdx >= 0 ? rel.slice(0, slashIdx) : '';
if (low.endsWith('.zip')) {
try {
const nested = await listZipFiles(f);
for (const nf of nested) {
const nm = relDir ? relDir + '/' + nf.name : nf.name;
addFileWithDedup(new File([nf], nm, { lastModified: nf.lastModified }));
added++;
}
} catch (err) {
addFileWithDedup(new File([f], rel, { lastModified: f.lastModified })); // zip как есть
added++;
}
} else if (DOC_EXTS.some(e => low.endsWith(e))) {
addFileWithDedup(new File([f], rel, { lastModified: f.lastModified }));
added++;
}
// иначе — не документ, пропускаем
}
} finally {
document.body.style.cursor = '';
}
folderInput.value = ''; // чтобы повторный выбор той же папки сработал
rr();
if (added) { st.className = 'status done'; st.textContent = '✅ Добавлено из папки: ' + added + ' файлов'; }
});
function ss(idx, h) { const e = document.getElementById('st-' + idx); if (e) e.innerHTML = h; }
async function uploadFiles() {