feat(files): gerenciador de arquivos/anexos com árvore de pastas, upload, preview e picker

- store/files.go: pastas (parent, anti-ciclo), FileMeta (nota opcional = avulso),
  blobs com extensão neutralizada p/ tipos perigosos, sanitização de nome
- api/files.go: upload multipart (64MiB), tree, static com ETag/Range + CSP
  sandbox + attachment p/ html/svg/js (stored-XSS mitigado), move/rename/delete,
  CRUD de pastas
- web: overlay Ctrl+J / 📎 (modo seletor insere sintaxe md/html/txt e associa),
  filtros Todos/Anexos/Avulsos/Nota atual, busca, lightbox img/video/audio/pdf/texto,
  colar-e-arrastar no editor e no gerenciador, ações por card (preview/associar/
  mover/renomear/excluir), mobile-first (ações sempre visíveis)
- fix: api() já serializa o body — chamadas do gerenciador passavam JSON pré-string
  (dupla codificação); auth por cookie (sem Bearer manual no fetch)
- sw v10; testes store+api (traversal, ciclo, XSS headers, range); README
This commit is contained in:
lgcosta committed 2026-11-05 10:32:40 -03:00
1 parent ea6b3b915d
commit 119e6cefe9
10 files changed
+1605 -4

No files matched your search

+32
View File
@@ -113,6 +113,36 @@
</form>
</div>
<!-- Gerenciador de arquivos / anexos -->
<div id="files-overlay" class="files-overlay hidden">
<div class="files-head">
<span class="files-title">Arquivos</span>
<div id="files-filters" class="files-filters"></div>
<input id="files-search" type="search" placeholder="Buscar…" autocomplete="off">
<button type="button" class="btn btn-primary btn-small" id="files-upload-btn">⬆ Enviar</button>
<button type="button" class="btn btn-small" id="files-newfolder">+ Pasta</button>
<button type="button" class="btn btn-icon btn-small" id="files-close" title="Fechar">✕</button>
</div>
<div class="files-body">
<aside id="files-tree" class="files-tree"></aside>
<section id="files-list" class="files-list dropzone">
<div id="files-empty" class="files-empty muted">Solte arquivos aqui ou use “⬆ Enviar”.</div>
</section>
</div>
<input type="file" id="files-input" multiple class="hidden">
</div>
<!-- Lightbox de preview -->
<div id="lightbox" class="overlay hidden">
<div class="lightbox-box">
<div id="lightbox-body" class="lightbox-body"></div>
<div class="dialog-actions">
<a id="lightbox-dl" class="btn btn-primary" download>Baixar</a>
<button type="button" id="lightbox-close" class="btn">Fechar</button>
</div>
</div>
</div>
<!-- Modal genérico (Sobre / confirmar) -->
<div id="modal-overlay" class="overlay hidden">
<div class="dialog">
@@ -146,6 +176,7 @@
<li data-action="new-note">Nova nota<span class="accel">Ctrl+N</span></li>
<li data-action="save">Salvar<span class="accel">Ctrl+S</span></li>
<li data-action="export">Baixar arquivo (.txt/.md/.html)…</li>
<li data-action="files">Gerenciador de arquivos…<span class="accel">Ctrl+J</span></li>
<li class="menu-sep"></li>
<li data-action="close-tab">Fechar aba<span class="accel">Alt+W</span></li>
<li data-action="delete-note">Excluir esta nota…</li>
@@ -192,6 +223,7 @@
<button type="button" class="btn btn-icon" id="btn-new" title="Nova nota (Ctrl+N)">✚<span class="tb-label">Nova</span></button>
<button type="button" class="btn btn-icon" id="btn-save" title="Salvar (Ctrl+S)">💾<span class="tb-label">Salvar</span></button>
<button type="button" class="btn btn-icon" id="btn-export" title="Baixar arquivo">⬇<span class="tb-label">Baixar</span></button>
<button type="button" class="btn btn-icon" id="btn-attach" title="Inserir anexo (📎)">📎<span class="tb-label">Anexo</span></button>
</div>
<span class="tb-sep"></span>
<div class="tb-group" id="fmt-tools">