Revert "v0.0.47: выбор целой папки (webkitdirectory) — рекурсивно, как раскрытие ZIP"
Deploy drhider / validate (push) Canceled after 0s
Deploy drhider / validate (push) Canceled after 0s
This reverts commit b5a14cfc3d.
This commit is contained in:
@@ -1,45 +0,0 @@
|
|||||||
# v0.0.47 — Выбор целой папки (рекурсивно, как раскрытие ZIP) — 2026-08-19
|
|
||||||
|
|
||||||
## Проблема
|
|
||||||
Нативный `<input type="file">` (`site/templates/index.html:133`) не умеет выбирать
|
|
||||||
папку целиком: клик по папке просто «входит» внутрь. Нужно выбирать целую папку
|
|
||||||
и рекурсивно забирать все файлы — как при раскрытии архива.
|
|
||||||
|
|
||||||
## Решение
|
|
||||||
- Второй `<input type="file" id="folderInput" webkitdirectory multiple>` + кнопка
|
|
||||||
«📁 Выбрать папку» (вызов `folderInput.click()`).
|
|
||||||
`webkitdirectory` взаимоисключающ с обычным выбором файлов на одном input —
|
|
||||||
поэтому отдельный input, а не переключатель.
|
|
||||||
- Браузер сам отдаёт ВСЕ файлы папки рекурсивно (включая подпапки) с полем
|
|
||||||
`file.webkitRelativePath`. Имя добавляемого файла = `webkitRelativePath`
|
|
||||||
(структура папок сохраняется до итогового ZIP, как у архива сохраняется
|
|
||||||
внутренний `e.name`).
|
|
||||||
- Обработка — та же логика, что у раскрытия ZIP (`listZipFiles`):
|
|
||||||
- `.zip` → рекурсивное раскрытие (вложенные zip тоже);
|
|
||||||
- документы `.pdf .doc .docx .txt .md` → в таблицу;
|
|
||||||
- прочее (картинки и т.п.) → пропускается.
|
|
||||||
- Сортировка входящих файлов по `webkitRelativePath` — детерминированный порядок.
|
|
||||||
|
|
||||||
## Файлы
|
|
||||||
- `site/templates/index.html`
|
|
||||||
- CSS: `.folder-btn` (полная ширина), `#folderInput { display:none }`
|
|
||||||
- HTML: скрытый `#folderInput` + кнопка в `.file-input-wrap`
|
|
||||||
- JS: `const folderInput`, обработчик `change` (рекурсия+фильтр+имена с путями)
|
|
||||||
- `resetAll()` и `load` — сброс `folderInput.value`
|
|
||||||
- `site/app.py` — VERSION 0.0.46 → 0.0.47
|
|
||||||
|
|
||||||
## Бэкенд — не менялся
|
|
||||||
- `extract_text` (drhider/extractor.py:204) берёт расширение через
|
|
||||||
`os.path.splitext` — пути в имени не мешают.
|
|
||||||
- `build_zip` (drhider/builder.py) пишет `ZipInfo` с путями как есть;
|
|
||||||
`_resolve_name` дедуплицирует по полному имени — одноимённые файлы в разных
|
|
||||||
подпапках не сталкиваются.
|
|
||||||
- Аплоад идёт из массива `sf` (`uploadFiles`), а не из `fi.files` — файлы из
|
|
||||||
папки уходят на бэк обычным путём.
|
|
||||||
|
|
||||||
## Проверка
|
|
||||||
1. «Выбрать папку» → папка с подпапками, вложенными .zip и картинками →
|
|
||||||
в таблице только документы, рекурсивно; .zip раскрыт; картинки пропущены;
|
|
||||||
имена с путями.
|
|
||||||
2. Обфускация → скачать ZIP → вложенность папок сохранена, всё обфусцировано.
|
|
||||||
3. Регрессия: обычный «Выбрать файлы» работает как раньше.
|
|
||||||
+1
-1
@@ -20,7 +20,7 @@ if _sys_path_root not in sys.path:
|
|||||||
sys.path.insert(0, _sys_path_root)
|
sys.path.insert(0, _sys_path_root)
|
||||||
|
|
||||||
# Версия приложения (меняется при изменениях)
|
# Версия приложения (меняется при изменениях)
|
||||||
VERSION = "0.0.47"
|
VERSION = "0.0.46"
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
|
|||||||
@@ -44,8 +44,6 @@
|
|||||||
.sub { font-size: 12px; color: var(--muted); margin-bottom: 10px; line-height: 1.5; }
|
.sub { font-size: 12px; color: var(--muted); margin-bottom: 10px; line-height: 1.5; }
|
||||||
.file-input-wrap { margin-bottom: 10px; }
|
.file-input-wrap { margin-bottom: 10px; }
|
||||||
.file-input-wrap input[type="file"] { width: 100%; font-size: 13px; }
|
.file-input-wrap input[type="file"] { width: 100%; font-size: 13px; }
|
||||||
.folder-btn { margin-top: 6px; width: 100%; }
|
|
||||||
#folderInput { display: none; }
|
|
||||||
.table-wrap { border: 1px solid var(--brand-gray); border-radius: 8px; overflow: hidden; }
|
.table-wrap { border: 1px solid var(--brand-gray); border-radius: 8px; overflow: hidden; }
|
||||||
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
th {
|
th {
|
||||||
@@ -133,8 +131,6 @@
|
|||||||
</p>
|
</p>
|
||||||
<div class="file-input-wrap">
|
<div class="file-input-wrap">
|
||||||
<input type="file" id="fileInput" multiple accept=".doc,.docx,.pdf,.txt,.zip">
|
<input type="file" id="fileInput" multiple accept=".doc,.docx,.pdf,.txt,.zip">
|
||||||
<input type="file" id="folderInput" webkitdirectory multiple>
|
|
||||||
<button class="btn folder-btn" id="folderBtn" type="button" onclick="document.getElementById('folderInput').click()">📁 Выбрать папку</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
@@ -192,7 +188,6 @@ const fl = document.getElementById('fileList');
|
|||||||
const fc = document.getElementById('fileCount');
|
const fc = document.getElementById('fileCount');
|
||||||
const ub = document.getElementById('uploadBtn');
|
const ub = document.getElementById('uploadBtn');
|
||||||
const st = document.getElementById('status');
|
const st = document.getElementById('status');
|
||||||
const folderInput = document.getElementById('folderInput');
|
|
||||||
const db = document.getElementById('dlBtns');
|
const db = document.getElementById('dlBtns');
|
||||||
let sf = [];
|
let sf = [];
|
||||||
let fileMeta = new Map(); // имя -> {size, mtime} для дедупа/суффиксов
|
let fileMeta = new Map(); // имя -> {size, mtime} для дедупа/суффиксов
|
||||||
@@ -207,7 +202,6 @@ function resetAll() {
|
|||||||
sf = [];
|
sf = [];
|
||||||
fileMeta = new Map();
|
fileMeta = new Map();
|
||||||
fi.value = '';
|
fi.value = '';
|
||||||
folderInput.value = '';
|
|
||||||
rr();
|
rr();
|
||||||
st.className = '';
|
st.className = '';
|
||||||
st.textContent = '';
|
st.textContent = '';
|
||||||
@@ -231,7 +225,7 @@ function rr() {
|
|||||||
|
|
||||||
function rm(i) { fileMeta.delete(sf[i].name); sf.splice(i, 1); const d = new DataTransfer(); sf.forEach(f => d.items.add(f)); fi.files = d.files; rr(); }
|
function rm(i) { fileMeta.delete(sf[i].name); sf.splice(i, 1); const d = new DataTransfer(); sf.forEach(f => d.items.add(f)); fi.files = d.files; rr(); }
|
||||||
|
|
||||||
window.addEventListener('load', () => { sf = []; fileMeta = new Map(); fi.value = ''; folderInput.value = ''; rr(); /* прогрев upstream-соединения */ fetch('/health').catch(() => {}); });
|
window.addEventListener('load', () => { sf = []; fileMeta = new Map(); fi.value = ''; rr(); /* прогрев upstream-соединения */ fetch('/health').catch(() => {}); });
|
||||||
// ═══════════ Нативная распаковка ZIP (без внешних библиотек) ═══════════
|
// ═══════════ Нативная распаковка ZIP (без внешних библиотек) ═══════════
|
||||||
|
|
||||||
function dosToMs(date, time) {
|
function dosToMs(date, time) {
|
||||||
@@ -367,34 +361,6 @@ fi.addEventListener('change', async () => {
|
|||||||
rr();
|
rr();
|
||||||
});
|
});
|
||||||
|
|
||||||
// ═══ Выбор целой папки: рекурсивно, как раскрытие ZIP ═══
|
|
||||||
// webkitdirectory отдаёт все файлы папки (включая подпапки) с webkitRelativePath.
|
|
||||||
folderInput.addEventListener('change', async () => {
|
|
||||||
const docExts = ['.pdf', '.doc', '.docx', '.txt', '.md'];
|
|
||||||
const incoming = Array.from(folderInput.files).sort((a, b) =>
|
|
||||||
(a.webkitRelativePath || a.name).localeCompare(b.webkitRelativePath || b.name));
|
|
||||||
for (const f of incoming) {
|
|
||||||
const rel = f.webkitRelativePath || f.name; // сохраняем структуру папок
|
|
||||||
const low = rel.toLowerCase();
|
|
||||||
if (low.endsWith('.zip')) {
|
|
||||||
try {
|
|
||||||
const nested = await listZipFiles(new File([f], rel, { lastModified: f.lastModified }));
|
|
||||||
nested.forEach(x => addFileWithDedup(x));
|
|
||||||
} catch (err) {
|
|
||||||
addFileWithDedup(new File([f], rel, { lastModified: f.lastModified })); // не удалось — добавить как есть
|
|
||||||
}
|
|
||||||
} else if (docExts.some(ext => low.endsWith(ext))) {
|
|
||||||
addFileWithDedup(new File([f], rel, { lastModified: f.lastModified }));
|
|
||||||
}
|
|
||||||
// иначе — не документ, пропускаем
|
|
||||||
}
|
|
||||||
const d = new DataTransfer();
|
|
||||||
sf.forEach(f => d.items.add(f));
|
|
||||||
fi.files = d.files;
|
|
||||||
folderInput.files = d.files;
|
|
||||||
rr();
|
|
||||||
});
|
|
||||||
|
|
||||||
function ss(idx, h) { const e = document.getElementById('st-' + idx); if (e) e.innerHTML = h; }
|
function ss(idx, h) { const e = document.getElementById('st-' + idx); if (e) e.innerHTML = h; }
|
||||||
|
|
||||||
async function uploadFiles() {
|
async function uploadFiles() {
|
||||||
|
|||||||
Reference in New Issue
Block a user