documents: вкладка «Мой отдел» и drag-and-drop загрузка в список.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-10-06 18:02:04 +03:00
parent ea09e30891
commit 0424e0273e
8 changed files with 362 additions and 57 deletions
+10
View File
@@ -1332,6 +1332,16 @@
border-color: var(--color-primary);
background: color-mix(in srgb, var(--color-primary) 8%, var(--color-muted));
}
.nc-browser-drop {
position: relative;
min-height: 4rem;
border-radius: 0.5rem;
transition: background 120ms, box-shadow 120ms;
}
.nc-browser-drop.is-drag {
background: color-mix(in srgb, var(--color-primary) 6%, transparent);
box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--color-primary) 45%, transparent);
}
.nc-drop-label {
display: flex;
align-items: center;
+10
View File
@@ -1968,6 +1968,16 @@
border-color: var(--color-primary);
background: color-mix(in srgb, var(--color-primary) 8%, var(--color-muted));
}
.nc-browser-drop {
position: relative;
min-height: 4rem;
border-radius: 0.5rem;
transition: background 120ms, box-shadow 120ms;
}
.nc-browser-drop.is-drag {
background: color-mix(in srgb, var(--color-primary) 6%, transparent);
box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--color-primary) 45%, transparent);
}
.nc-drop-label {
display: flex;
align-items: center;
+87 -17
View File
@@ -1,45 +1,116 @@
(function () {
document.documentElement.classList.remove('oo-open');
var emptyLabel = 'или перетащите сюда';
var emptyLabel = 'или перетащите сюда / в список';
function dropRoot(el) {
return el && el.closest ? el.closest('.nc-drop') : null;
}
function browserDropRoot(el) {
return el && el.closest ? el.closest('.nc-browser-drop[data-nc-upload="1"]') : null;
}
function findUploadForm(fromEl) {
var browser = browserDropRoot(fromEl);
if (browser) {
var f = browser.querySelector('form.nc-drop');
if (f) return f;
}
return dropRoot(fromEl);
}
function uploadFilesViaForm(form, fileList) {
if (!form || !fileList || !fileList.length) return;
var files = Array.prototype.slice.call(fileList);
var url = form.getAttribute('hx-post') || form.getAttribute('action');
var targetSel = form.getAttribute('hx-target') || '#documents-browser';
var swap = form.getAttribute('hx-swap') || 'innerHTML';
function next() {
if (!files.length) return;
var file = files.shift();
var fd = new FormData(form);
fd.set('file', file, file.name);
fetch(url, {
method: 'POST',
body: fd,
credentials: 'same-origin',
headers: { 'HX-Request': 'true' },
}).then(function (r) {
return r.text().then(function (html) {
var target = document.querySelector(targetSel);
if (target) {
if (typeof htmx !== 'undefined' && htmx.swap) {
htmx.swap(target, html, { swapStyle: swap });
} else {
target.innerHTML = html;
}
// после swap форма пересоздана — продолжаем очередь
form = target.querySelector('form.nc-drop') || form;
}
if (files.length) next();
});
}).catch(function () { /* ignore */ });
}
next();
}
document.addEventListener('change', function (e) {
if (!e.target || !e.target.classList.contains('nc-drop-input')) return;
var drop = dropRoot(e.target);
var nameEl = drop && drop.querySelector('.nc-drop-name');
if (!nameEl) return;
var file = e.target.files && e.target.files[0];
nameEl.textContent = file ? file.name : emptyLabel;
var files = e.target.files;
if (!files || !files.length) {
nameEl.textContent = emptyLabel;
return;
}
nameEl.textContent = files.length === 1
? files[0].name
: (files.length + ' файлов');
});
document.addEventListener('dragover', function (e) {
var drop = dropRoot(e.target);
if (!drop) return;
var zone = browserDropRoot(e.target) || dropRoot(e.target);
if (!zone) return;
e.preventDefault();
drop.classList.add('is-drag');
e.dataTransfer.dropEffect = 'copy';
zone.classList.add('is-drag');
});
document.addEventListener('dragleave', function (e) {
var drop = dropRoot(e.target);
if (!drop) return;
if (e.relatedTarget && drop.contains(e.relatedTarget)) return;
drop.classList.remove('is-drag');
var zone = browserDropRoot(e.target) || dropRoot(e.target);
if (!zone) return;
if (e.relatedTarget && zone.contains(e.relatedTarget)) return;
zone.classList.remove('is-drag');
});
document.addEventListener('drop', function (e) {
var browser = browserDropRoot(e.target);
var drop = dropRoot(e.target);
if (!drop) return;
var zone = browser || drop;
if (!zone) return;
e.preventDefault();
drop.classList.remove('is-drag');
var input = drop.querySelector('.nc-drop-input');
if (!input || !e.dataTransfer || !e.dataTransfer.files.length) return;
input.files = e.dataTransfer.files;
input.dispatchEvent(new Event('change', { bubbles: true }));
zone.classList.remove('is-drag');
if (!e.dataTransfer || !e.dataTransfer.files.length) return;
var form = findUploadForm(e.target);
if (!form) return;
// автозагрузка сразу (без кнопки «Загрузить»)
uploadFilesViaForm(form, e.dataTransfer.files);
});
// Несколько файлов через кнопку «Загрузить» — по одному, как при DnD
document.addEventListener('submit', function (e) {
var form = e.target;
if (!form || !form.classList || !form.classList.contains('nc-drop')) return;
var input = form.querySelector('.nc-drop-input');
if (!input || !input.files || input.files.length <= 1) return;
e.preventDefault();
uploadFilesViaForm(form, input.files);
}, true);
document.addEventListener('htmx:afterSwap', function (e) {
if (!e.detail || !e.detail.target || e.detail.target.id !== 'main-content') return;
document.documentElement.classList.remove('oo-open');
@@ -127,7 +198,6 @@
dl.setAttribute('download', name || '');
}
if (full) {
// полная страница — тот же inline URL (или отдельный preview route, если передан)
var fullHref = (elOrUrl && elOrUrl.getAttribute && elOrUrl.getAttribute('data-doc-full')) || url;
full.href = fullHref;
}