feat: aplicação inicial — gerenciador de notas web estilo Mousepad
- Backend Go: API REST, auth por token (Bearer + cookie HttpOnly), storage em disco com revisões content-addressed (estilo Git: hash, autor, data, diff, restauração) - Editor txt/md/html com preview, tags #tag, categorias, painel de histórico - Preferências: números de linha, marcador de coluna (padrão 80), quebra de linha, fonte/tamanho, 4 temas e colorização de sintaxe (overlay no textarea) - Frontend vanilla embutido via go:embed; Dockerfile multi-stage + docker-compose
This commit is contained in:
commit
9bc5de4c56
22 files changed
+4239
No files matched your search
@@ -0,0 +1,56 @@
|
||||
// Package diffx gera diffs unificados no estilo Git a partir de strings.
|
||||
package diffx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hexops/gotextdiff"
|
||||
"github.com/hexops/gotextdiff/myers"
|
||||
"github.com/hexops/gotextdiff/span"
|
||||
)
|
||||
|
||||
// Unified devolve o diff unificado (contexto padrão de 3 linhas) entre
|
||||
// before e after, rotulando os lados como oldName e newName.
|
||||
func Unified(oldName, newName, before, after string) string {
|
||||
if before == after {
|
||||
return ""
|
||||
}
|
||||
edits := myers.ComputeEdits(span.URIFromPath(oldName), before, after)
|
||||
unified := gotextdiff.ToUnified(oldName, newName, before, edits)
|
||||
return fmt.Sprintf("%s", unified)
|
||||
}
|
||||
|
||||
// Stats conta linhas adicionadas e removidas em um diff unificado já gerado.
|
||||
func Stats(unified string) (added, removed int) {
|
||||
for _, line := range splitLines(unified) {
|
||||
switch {
|
||||
case len(line) == 0:
|
||||
case line[0] == '+' && !isHeader(line):
|
||||
added++
|
||||
case line[0] == '-' && !isHeader(line):
|
||||
removed++
|
||||
}
|
||||
}
|
||||
return added, removed
|
||||
}
|
||||
|
||||
func isHeader(line string) bool {
|
||||
// cabeçalhos de arquivo no formato "--- nome" / "+++ nome" começam com
|
||||
// dois sinais; uma linha de conteúdo "++foo" é contada como adição.
|
||||
return len(line) >= 3 && line[0] == line[1] && (line[0] == '+' || line[0] == '-')
|
||||
}
|
||||
|
||||
func splitLines(s string) []string {
|
||||
var out []string
|
||||
start := 0
|
||||
for i := range len(s) {
|
||||
if s[i] == '\n' {
|
||||
out = append(out, s[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
if start < len(s) {
|
||||
out = append(out, s[start:])
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package diffx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnifiedEmptyWhenEqual(t *testing.T) {
|
||||
if got := Unified("a.txt", "a.txt", "mesmo", "mesmo"); got != "" {
|
||||
t.Fatalf("esperava diff vazio, veio %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnifiedCreateLooksLikeGit(t *testing.T) {
|
||||
got := Unified("(sem conteúdo)", "nota.md", "", "# Ola\n\nlinha\n")
|
||||
for _, want := range []string{"--- (sem conteúdo)", "+++ nota.md", "@@", "+# Ola", "+linha"} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Errorf("diff deveria conter %q; diff:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
if strings.Contains(got, "-linha") {
|
||||
t.Errorf("não deveria haver remoção em criação pura:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnifiedEditHasAddAndRemove(t *testing.T) {
|
||||
before := "alpha\nbravo\ncharlie\n"
|
||||
after := "alpha\nBRAVO change\ncharlie\ndelta\n"
|
||||
got := Unified("n", "n", before, after)
|
||||
added, removed := Stats(got)
|
||||
if removed != 1 {
|
||||
t.Errorf("esperava 1 remoção, obtive %d (diff:\n%s)", removed, got)
|
||||
}
|
||||
if added != 2 {
|
||||
t.Errorf("esperava 2 adições, obtive %d (diff:\n%s)", added, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatsIgnoresFileHeaders(t *testing.T) {
|
||||
added, removed := Stats("--- a\n+++ b\n@@ -1 +1 @@\n+x\n")
|
||||
if added != 1 || removed != 0 {
|
||||
t.Fatalf("added=%d removed=%d, esperava 1/0", added, removed)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user