explicai 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +53 -2
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +51 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/annotations.d.ts +7 -0
- package/dist/core/annotations.js +108 -0
- package/dist/core/annotations.js.map +1 -0
- package/dist/core/assets.d.ts +15 -0
- package/dist/core/assets.js +81 -0
- package/dist/core/assets.js.map +1 -0
- package/dist/core/export/collect.d.ts +27 -0
- package/dist/core/export/collect.js +49 -0
- package/dist/core/export/collect.js.map +1 -0
- package/dist/core/export/html.d.ts +6 -0
- package/dist/core/export/html.js +195 -0
- package/dist/core/export/html.js.map +1 -0
- package/dist/core/export/imageSize.d.ts +9 -0
- package/dist/core/export/imageSize.js +55 -0
- package/dist/core/export/imageSize.js.map +1 -0
- package/dist/core/export/index.d.ts +23 -0
- package/dist/core/export/index.js +71 -0
- package/dist/core/export/index.js.map +1 -0
- package/dist/core/export/labels.d.ts +16 -0
- package/dist/core/export/labels.js +29 -0
- package/dist/core/export/labels.js.map +1 -0
- package/dist/core/export/markdown.d.ts +8 -0
- package/dist/core/export/markdown.js +74 -0
- package/dist/core/export/markdown.js.map +1 -0
- package/dist/core/export/pdf.d.ts +6 -0
- package/dist/core/export/pdf.js +67 -0
- package/dist/core/export/pdf.js.map +1 -0
- package/dist/core/export/refs.d.ts +24 -0
- package/dist/core/export/refs.js +53 -0
- package/dist/core/export/refs.js.map +1 -0
- package/dist/core/frontmatter.d.ts +5 -0
- package/dist/core/frontmatter.js +17 -0
- package/dist/core/frontmatter.js.map +1 -0
- package/dist/core/fs.d.ts +9 -0
- package/dist/core/fs.js +33 -0
- package/dist/core/fs.js.map +1 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/manuals.d.ts +17 -0
- package/dist/core/manuals.js +66 -0
- package/dist/core/manuals.js.map +1 -0
- package/dist/core/pages.d.ts +38 -0
- package/dist/core/pages.js +138 -0
- package/dist/core/pages.js.map +1 -0
- package/dist/core/text.d.ts +3 -0
- package/dist/core/text.js +19 -0
- package/dist/core/text.js.map +1 -0
- package/dist/core/trash.d.ts +25 -0
- package/dist/core/trash.js +79 -0
- package/dist/core/trash.js.map +1 -0
- package/dist/core/tree.d.ts +13 -0
- package/dist/core/tree.js +28 -0
- package/dist/core/tree.js.map +1 -0
- package/dist/core/types.d.ts +86 -0
- package/dist/core/types.js +5 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/versions.d.ts +27 -0
- package/dist/core/versions.js +82 -0
- package/dist/core/versions.js.map +1 -0
- package/dist/server/http.d.ts +23 -0
- package/dist/server/http.js +78 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +30 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/router.d.ts +30 -0
- package/dist/server/router.js +28 -0
- package/dist/server/router.js.map +1 -0
- package/dist/server/routes/assets.d.ts +2 -0
- package/dist/server/routes/assets.js +44 -0
- package/dist/server/routes/assets.js.map +1 -0
- package/dist/server/routes/export.d.ts +2 -0
- package/dist/server/routes/export.js +33 -0
- package/dist/server/routes/export.js.map +1 -0
- package/dist/server/routes/manuals.d.ts +2 -0
- package/dist/server/routes/manuals.js +40 -0
- package/dist/server/routes/manuals.js.map +1 -0
- package/dist/server/routes/pages.d.ts +2 -0
- package/dist/server/routes/pages.js +77 -0
- package/dist/server/routes/pages.js.map +1 -0
- package/dist/server/routes/versions.d.ts +2 -0
- package/dist/server/routes/versions.js +43 -0
- package/dist/server/routes/versions.js.map +1 -0
- package/dist/server/routes/web.d.ts +3 -0
- package/dist/server/routes/web.js +23 -0
- package/dist/server/routes/web.js.map +1 -0
- package/dist/server/security.d.ts +6 -0
- package/dist/server/security.js +16 -0
- package/dist/server/security.js.map +1 -0
- package/dist/server/workspace.d.ts +5 -0
- package/dist/server/workspace.js +17 -0
- package/dist/server/workspace.js.map +1 -0
- package/dist/web/assets/archivo-latin-ext-wdth-normal-7khWdh9v.woff2 +0 -0
- package/dist/web/assets/archivo-latin-wdth-normal-DY7AcnAa.woff2 +0 -0
- package/dist/web/assets/archivo-vietnamese-wdth-normal-rJmnGBSt.woff2 +0 -0
- package/dist/web/assets/atkinson-hyperlegible-next-latin-400-normal-DPp7N0cD.woff +0 -0
- package/dist/web/assets/atkinson-hyperlegible-next-latin-400-normal-FfmJh7DR.woff2 +0 -0
- package/dist/web/assets/atkinson-hyperlegible-next-latin-700-normal-C2qZD_Q9.woff +0 -0
- package/dist/web/assets/atkinson-hyperlegible-next-latin-700-normal-Dpiyiu63.woff2 +0 -0
- package/dist/web/assets/atkinson-hyperlegible-next-latin-ext-400-normal-BalHKn7d.woff2 +0 -0
- package/dist/web/assets/atkinson-hyperlegible-next-latin-ext-400-normal-qxMiU51n.woff +0 -0
- package/dist/web/assets/atkinson-hyperlegible-next-latin-ext-700-normal-292QVhS1.woff +0 -0
- package/dist/web/assets/atkinson-hyperlegible-next-latin-ext-700-normal-CBw-mJf_.woff2 +0 -0
- package/dist/web/assets/index-BXkBk-2Z.css +1 -0
- package/dist/web/assets/index-C02XKPHS.js +260 -0
- package/dist/web/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/dist/web/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/dist/web/favicon.svg +1 -0
- package/dist/web/index.html +24 -0
- package/package.json +90 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gabriel Sampaio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,54 @@
|
|
|
1
|
-
|
|
1
|
+
<p>
|
|
2
|
+
<img src="https://gabriel-codesampaio.github.io/explicai-brandbook/logo/explicai-logo-horizontal.svg" alt="Explicaí" height="56">
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
|
-
|
|
5
|
+
Construtor de manuais open source e gratuito. Do print ao manual pronto em PDF, HTML e Markdown.
|
|
6
|
+
|
|
7
|
+
> Tira o print, que o Explicaí explica.
|
|
8
|
+
|
|
9
|
+
## Como usar
|
|
10
|
+
|
|
11
|
+
Precisa do Node 20.12 ou mais novo.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx explicai manuais
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
O Explicaí Studio abre no navegador, e os manuais ficam na pasta `manuais` (sem pasta, vale a pasta atual). Opções: `--port 4321` para trocar a porta e `--no-open` para não abrir o navegador.
|
|
18
|
+
|
|
19
|
+
No Studio você:
|
|
20
|
+
|
|
21
|
+
- escreve cada tela ou tarefa do sistema numa página, organizada em árvore;
|
|
22
|
+
- cola prints e marca com números, destaques, setas e áreas clicáveis;
|
|
23
|
+
- escreve passos numerados com o print e o resultado esperado de cada um;
|
|
24
|
+
- guarda versões de cada página e escolhe qual vai para o manual final;
|
|
25
|
+
- marca cada página como rascunho, em revisão, aprovada ou publicada;
|
|
26
|
+
- exporta em HTML, PDF ou Markdown. O PDF usa o Chrome, o Edge ou o Chromium que já estiver na máquina.
|
|
27
|
+
|
|
28
|
+
Cada manual é uma pasta de arquivos Markdown que você pode versionar no git junto com o código. O formato está em [docs/manual-format.md](docs/manual-format.md).
|
|
29
|
+
|
|
30
|
+
A interface está em português e em inglês, e segue o idioma do navegador. O [Explicaí Agent](https://github.com/gabriel-codesampaio/explicai-mcp) vai deixar uma IA montar manuais no mesmo formato, pelo MCP.
|
|
31
|
+
|
|
32
|
+
## Desenvolvimento
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install
|
|
36
|
+
npm run check # lint, tipos e testes
|
|
37
|
+
npm run test:e2e # testes de ponta a ponta no Chrome instalado
|
|
38
|
+
npm run build
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Para trabalhar no editor: `npm run dev:server` em um terminal e `npm run dev:web` em outro.
|
|
42
|
+
|
|
43
|
+
Contribuições são bem-vindas. Abra uma issue para conversar sobre a ideia, ou mande um pull request com `npm run check` passando.
|
|
44
|
+
|
|
45
|
+
- Formato do manual em disco: [docs/manual-format.md](docs/manual-format.md)
|
|
46
|
+
- Brand book: [gabriel-codesampaio.github.io/explicai-brandbook](https://gabriel-codesampaio.github.io/explicai-brandbook/)
|
|
47
|
+
|
|
48
|
+
## English
|
|
49
|
+
|
|
50
|
+
Explicaí is a free, open source manual builder. Run `npx explicai manuals` (Node 20.12+), write one page per screen or task, paste screenshots and mark them up with numbered markers, highlights, arrows and clickable areas, then export to HTML, PDF or Markdown. Each manual is a folder of Markdown files you can keep in git next to your code. The interface is available in Portuguese and English. Pronounced *ex-plee-kah-EE*.
|
|
51
|
+
|
|
52
|
+
## Licença
|
|
53
|
+
|
|
54
|
+
MIT. Um projeto [codesampa.io](https://codesampa.io).
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdir } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { parseArgs } from "node:util";
|
|
7
|
+
import { createStudioServer } from "./server/index.js";
|
|
8
|
+
const { values, positionals } = parseArgs({
|
|
9
|
+
allowPositionals: true,
|
|
10
|
+
options: {
|
|
11
|
+
port: { type: "string", short: "p", default: "4321" },
|
|
12
|
+
"no-open": { type: "boolean", default: false },
|
|
13
|
+
help: { type: "boolean", short: "h", default: false },
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
if (values.help) {
|
|
17
|
+
console.log(`Uso: explicai [pasta] [--port 4321] [--no-open]
|
|
18
|
+
|
|
19
|
+
Abre o Explicaí Studio no navegador. Os manuais ficam em [pasta] (padrão: pasta atual).`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
const workspace = path.resolve(positionals[0] ?? ".");
|
|
23
|
+
const port = Number(values.port);
|
|
24
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
25
|
+
console.error(`Porta inválida: ${values.port}`);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
await mkdir(workspace, { recursive: true });
|
|
29
|
+
const webDir = path.join(path.dirname(fileURLToPath(import.meta.url)), "web");
|
|
30
|
+
const server = createStudioServer({ workspace, webDir });
|
|
31
|
+
server.on("error", (err) => {
|
|
32
|
+
console.error(err.code === "EADDRINUSE" ? `A porta ${port} já está em uso. Tente --port ${port + 1}.` : err.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
server.listen(port, "127.0.0.1", () => {
|
|
36
|
+
const url = `http://localhost:${port}`;
|
|
37
|
+
console.log(`Explicaí Studio em ${url}\nManuais em ${workspace}\nCtrl+C para sair.`);
|
|
38
|
+
if (!values["no-open"])
|
|
39
|
+
openBrowser(url);
|
|
40
|
+
});
|
|
41
|
+
function openBrowser(url) {
|
|
42
|
+
const [cmd, args] = process.platform === "win32"
|
|
43
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
44
|
+
: process.platform === "darwin"
|
|
45
|
+
? ["open", [url]]
|
|
46
|
+
: ["xdg-open", [url]];
|
|
47
|
+
spawn(cmd, args, { stdio: "ignore", detached: true })
|
|
48
|
+
.on("error", () => { })
|
|
49
|
+
.unref();
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type Annotations } from "./types.js";
|
|
2
|
+
export declare function emptyAnnotations(): Annotations;
|
|
3
|
+
/**
|
|
4
|
+
* Valida anotações vindas do disco ou do Agent. Lança Error com o caminho do campo inválido.
|
|
5
|
+
* Coordenadas fora de 0..1 são rejeitadas para que nada aponte para fora da imagem.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseAnnotations(input: unknown): Annotations;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { FORMAT_VERSION } from "./types.js";
|
|
2
|
+
export function emptyAnnotations() {
|
|
3
|
+
return { version: FORMAT_VERSION, markers: [], highlights: [], arrows: [], hotspots: [] };
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Valida anotações vindas do disco ou do Agent. Lança Error com o caminho do campo inválido.
|
|
7
|
+
* Coordenadas fora de 0..1 são rejeitadas para que nada aponte para fora da imagem.
|
|
8
|
+
*/
|
|
9
|
+
export function parseAnnotations(input) {
|
|
10
|
+
const obj = record(input, "anotações");
|
|
11
|
+
if (obj.version !== FORMAT_VERSION)
|
|
12
|
+
throw new Error(`anotações.version: esperado ${FORMAT_VERSION}`);
|
|
13
|
+
const ids = new Set();
|
|
14
|
+
const id = (v, path) => {
|
|
15
|
+
const s = text(v, path);
|
|
16
|
+
if (ids.has(s))
|
|
17
|
+
throw new Error(`${path}: id repetido "${s}"`);
|
|
18
|
+
ids.add(s);
|
|
19
|
+
return s;
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
version: FORMAT_VERSION,
|
|
23
|
+
markers: list(obj.markers, "markers").map((m, i) => {
|
|
24
|
+
const r = record(m, `markers[${i}]`);
|
|
25
|
+
const n = r.n;
|
|
26
|
+
if (!Number.isInteger(n) || n < 1)
|
|
27
|
+
throw new Error(`markers[${i}].n: inteiro a partir de 1`);
|
|
28
|
+
return {
|
|
29
|
+
id: id(r.id, `markers[${i}].id`),
|
|
30
|
+
n: n,
|
|
31
|
+
...point(r, `markers[${i}]`),
|
|
32
|
+
...(r.label === undefined ? {} : { label: text(r.label, `markers[${i}].label`, true) }),
|
|
33
|
+
};
|
|
34
|
+
}),
|
|
35
|
+
highlights: list(obj.highlights, "highlights").map((h, i) => {
|
|
36
|
+
const r = record(h, `highlights[${i}]`);
|
|
37
|
+
return { id: id(r.id, `highlights[${i}].id`), ...rect(r, `highlights[${i}]`) };
|
|
38
|
+
}),
|
|
39
|
+
arrows: list(obj.arrows, "arrows").map((a, i) => {
|
|
40
|
+
const r = record(a, `arrows[${i}]`);
|
|
41
|
+
return {
|
|
42
|
+
id: id(r.id, `arrows[${i}].id`),
|
|
43
|
+
from: point(record(r.from, `arrows[${i}].from`), `arrows[${i}].from`),
|
|
44
|
+
to: point(record(r.to, `arrows[${i}].to`), `arrows[${i}].to`),
|
|
45
|
+
};
|
|
46
|
+
}),
|
|
47
|
+
hotspots: list(obj.hotspots, "hotspots").map((h, i) => {
|
|
48
|
+
const r = record(h, `hotspots[${i}]`);
|
|
49
|
+
return {
|
|
50
|
+
id: id(r.id, `hotspots[${i}].id`),
|
|
51
|
+
...rect(r, `hotspots[${i}]`),
|
|
52
|
+
target: target(r.target, `hotspots[${i}].target`),
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function target(v, path) {
|
|
58
|
+
const r = record(v, path);
|
|
59
|
+
switch (r.type) {
|
|
60
|
+
case "page":
|
|
61
|
+
return { type: "page", pageId: text(r.pageId, `${path}.pageId`) };
|
|
62
|
+
case "anchor":
|
|
63
|
+
return { type: "anchor", pageId: text(r.pageId, `${path}.pageId`), anchor: text(r.anchor, `${path}.anchor`) };
|
|
64
|
+
case "url": {
|
|
65
|
+
const href = text(r.href, `${path}.href`);
|
|
66
|
+
// Só http(s) e mailto: um href "javascript:" viraria XSS no HTML exportado.
|
|
67
|
+
if (!/^(https?:|mailto:)/i.test(href))
|
|
68
|
+
throw new Error(`${path}.href: use http, https ou mailto`);
|
|
69
|
+
return { type: "url", href };
|
|
70
|
+
}
|
|
71
|
+
default:
|
|
72
|
+
throw new Error(`${path}.type: esperado "page", "anchor" ou "url"`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function point(r, path) {
|
|
76
|
+
return { x: unit(r.x, `${path}.x`), y: unit(r.y, `${path}.y`) };
|
|
77
|
+
}
|
|
78
|
+
function rect(r, path) {
|
|
79
|
+
const box = { ...point(r, path), w: unit(r.w, `${path}.w`), h: unit(r.h, `${path}.h`) };
|
|
80
|
+
if (box.w === 0 || box.h === 0)
|
|
81
|
+
throw new Error(`${path}: largura e altura maiores que zero`);
|
|
82
|
+
if (box.x + box.w > 1 + 1e-9 || box.y + box.h > 1 + 1e-9)
|
|
83
|
+
throw new Error(`${path}: a área passa da borda da imagem`);
|
|
84
|
+
return box;
|
|
85
|
+
}
|
|
86
|
+
function unit(v, path) {
|
|
87
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 0 || v > 1)
|
|
88
|
+
throw new Error(`${path}: número entre 0 e 1`);
|
|
89
|
+
return v;
|
|
90
|
+
}
|
|
91
|
+
function text(v, path, allowEmpty = false) {
|
|
92
|
+
if (typeof v !== "string" || (!allowEmpty && v.trim() === ""))
|
|
93
|
+
throw new Error(`${path}: texto obrigatório`);
|
|
94
|
+
return v;
|
|
95
|
+
}
|
|
96
|
+
function list(v, path) {
|
|
97
|
+
if (v === undefined)
|
|
98
|
+
return [];
|
|
99
|
+
if (!Array.isArray(v))
|
|
100
|
+
throw new Error(`${path}: esperado uma lista`);
|
|
101
|
+
return v;
|
|
102
|
+
}
|
|
103
|
+
function record(v, path) {
|
|
104
|
+
if (typeof v !== "object" || v === null || Array.isArray(v))
|
|
105
|
+
throw new Error(`${path}: esperado um objeto`);
|
|
106
|
+
return v;
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=annotations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotations.js","sourceRoot":"","sources":["../../src/core/annotations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Annotations } from "./types.js";
|
|
2
|
+
/** Grava a imagem em `assets/` com nome único e devolve o caminho relativo ao manual. */
|
|
3
|
+
export declare function saveAsset(dir: string, fileName: string, data: Uint8Array): Promise<string>;
|
|
4
|
+
export declare function readAnnotations(dir: string, assetPath: string): Promise<Annotations>;
|
|
5
|
+
export declare function writeAnnotations(dir: string, assetPath: string, input: unknown): Promise<Annotations>;
|
|
6
|
+
/**
|
|
7
|
+
* Arquivos de assets/ que ninguém usa: imagens que nenhuma página, versão ou item da lixeira cita,
|
|
8
|
+
* e marcações (.json) cuja imagem não existe mais. Devolve caminhos como `assets/tela.png`.
|
|
9
|
+
*/
|
|
10
|
+
export declare function findUnusedAssets(dir: string): Promise<string[]>;
|
|
11
|
+
/** Move o que `findUnusedAssets` achou para `.lixeira/imagens-<data>/`. Nada é apagado. */
|
|
12
|
+
export declare function cleanUnusedAssets(dir: string): Promise<{
|
|
13
|
+
moved: number;
|
|
14
|
+
folder: string | null;
|
|
15
|
+
}>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { emptyAnnotations, parseAnnotations } from "./annotations.js";
|
|
4
|
+
import { atomicWrite, exists, relativePosix, resolveInside, uniquePath } from "./fs.js";
|
|
5
|
+
import { ASSETS_DIR } from "./manuals.js";
|
|
6
|
+
import { slugify } from "./text.js";
|
|
7
|
+
const IMAGE_EXT = /^\.(png|jpe?g|gif|webp|svg)$/;
|
|
8
|
+
/** Grava a imagem em `assets/` com nome único e devolve o caminho relativo ao manual. */
|
|
9
|
+
export async function saveAsset(dir, fileName, data) {
|
|
10
|
+
const ext = path.extname(fileName).toLowerCase();
|
|
11
|
+
if (!IMAGE_EXT.test(ext))
|
|
12
|
+
throw new Error(`Formato de imagem não suportado: ${ext || "sem extensão"}`);
|
|
13
|
+
const name = path.basename(fileName);
|
|
14
|
+
const abs = await uniquePath(path.join(dir, ASSETS_DIR), slugify(name.slice(0, name.length - ext.length)), ext);
|
|
15
|
+
await writeFile(abs, data, { flag: "wx" });
|
|
16
|
+
return relativePosix(dir, abs);
|
|
17
|
+
}
|
|
18
|
+
export async function readAnnotations(dir, assetPath) {
|
|
19
|
+
const file = annotationsFile(dir, assetPath);
|
|
20
|
+
if (!(await exists(file)))
|
|
21
|
+
return emptyAnnotations();
|
|
22
|
+
return parseAnnotations(JSON.parse(await readFile(file, "utf8")));
|
|
23
|
+
}
|
|
24
|
+
export async function writeAnnotations(dir, assetPath, input) {
|
|
25
|
+
const annotations = parseAnnotations(input);
|
|
26
|
+
await atomicWrite(annotationsFile(dir, assetPath), `${JSON.stringify(annotations, null, 2)}\n`);
|
|
27
|
+
return annotations;
|
|
28
|
+
}
|
|
29
|
+
/** `assets/foto.png` → `assets/foto.png.json`. Só imagens dentro de assets/ têm anotações. */
|
|
30
|
+
function annotationsFile(dir, assetPath) {
|
|
31
|
+
const abs = resolveInside(dir, assetPath);
|
|
32
|
+
if (path.dirname(abs) !== path.join(path.resolve(dir), ASSETS_DIR)) {
|
|
33
|
+
throw new Error("Anotações só existem para imagens em assets/.");
|
|
34
|
+
}
|
|
35
|
+
return `${abs}.json`;
|
|
36
|
+
}
|
|
37
|
+
/** Pastas cujos Markdown podem citar imagens: páginas, versões guardadas e lixeira. */
|
|
38
|
+
const MENTION_DIRS = ["pages", ".versoes", ".lixeira"];
|
|
39
|
+
const MENTION = /assets\/([^)\s"'#?]+)/g;
|
|
40
|
+
/**
|
|
41
|
+
* Arquivos de assets/ que ninguém usa: imagens que nenhuma página, versão ou item da lixeira cita,
|
|
42
|
+
* e marcações (.json) cuja imagem não existe mais. Devolve caminhos como `assets/tela.png`.
|
|
43
|
+
*/
|
|
44
|
+
export async function findUnusedAssets(dir) {
|
|
45
|
+
const assetsDir = path.join(dir, ASSETS_DIR);
|
|
46
|
+
if (!(await exists(assetsDir)))
|
|
47
|
+
return [];
|
|
48
|
+
const used = new Set();
|
|
49
|
+
for (const sub of MENTION_DIRS) {
|
|
50
|
+
const root = path.join(dir, sub);
|
|
51
|
+
if (!(await exists(root)))
|
|
52
|
+
continue;
|
|
53
|
+
for (const file of await readdir(root, { recursive: true })) {
|
|
54
|
+
if (!file.endsWith(".md"))
|
|
55
|
+
continue;
|
|
56
|
+
for (const [, name] of (await readFile(path.join(root, file), "utf8")).matchAll(MENTION)) {
|
|
57
|
+
if (name)
|
|
58
|
+
used.add(decodeURI(name));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const files = await readdir(assetsDir);
|
|
63
|
+
const names = new Set(files);
|
|
64
|
+
return files
|
|
65
|
+
.filter((f) => (f.endsWith(".json") ? !names.has(f.slice(0, -".json".length)) : !used.has(f)))
|
|
66
|
+
.flatMap((f) => (!f.endsWith(".json") && names.has(`${f}.json`) ? [f, `${f}.json`] : [f]))
|
|
67
|
+
.filter((f, i, all) => all.indexOf(f) === i)
|
|
68
|
+
.map((f) => `${ASSETS_DIR}/${f}`);
|
|
69
|
+
}
|
|
70
|
+
/** Move o que `findUnusedAssets` achou para `.lixeira/imagens-<data>/`. Nada é apagado. */
|
|
71
|
+
export async function cleanUnusedAssets(dir) {
|
|
72
|
+
const unused = await findUnusedAssets(dir);
|
|
73
|
+
if (unused.length === 0)
|
|
74
|
+
return { moved: 0, folder: null };
|
|
75
|
+
const folder = path.join(".lixeira", `imagens-${new Date().toISOString().replace(/[:.]/g, "-")}`);
|
|
76
|
+
await mkdir(path.join(dir, folder, ASSETS_DIR), { recursive: true });
|
|
77
|
+
for (const asset of unused)
|
|
78
|
+
await rename(resolveInside(dir, asset), path.join(dir, folder, asset));
|
|
79
|
+
return { moved: unused.length, folder: folder.split(path.sep).join("/") };
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../../src/core/assets.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ManualMeta } from "../types.js";
|
|
2
|
+
export interface ExportPage {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
/** Posição na árvore, como no sumário: "2", "2.1". */
|
|
6
|
+
number: string;
|
|
7
|
+
depth: number;
|
|
8
|
+
/** Nome do arquivo (`cadastrar-cliente.md`), para achar a página pelos links `./arquivo.md`. */
|
|
9
|
+
fileName: string;
|
|
10
|
+
body: string;
|
|
11
|
+
}
|
|
12
|
+
export interface Collected {
|
|
13
|
+
meta: ManualMeta;
|
|
14
|
+
pages: ExportPage[];
|
|
15
|
+
/** Páginas que ficaram de fora, com o motivo (arquivo apagado, versão sumida). */
|
|
16
|
+
warnings: string[];
|
|
17
|
+
}
|
|
18
|
+
export interface CollectOptions {
|
|
19
|
+
/** Só páginas aprovadas ou publicadas. Subpágina aprovada entra mesmo com a mãe em rascunho. */
|
|
20
|
+
onlyApproved?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* As páginas na ordem da árvore, com o texto que vai para o manual final: a versão marcada
|
|
24
|
+
* "No manual" quando existe, senão o texto atual. Página com problema fica de fora e vira aviso.
|
|
25
|
+
* A numeração conta só as páginas que entram, então o sumário não fica com buracos.
|
|
26
|
+
*/
|
|
27
|
+
export declare function collectPages(dir: string, opts?: CollectOptions): Promise<Collected>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readManual } from "../manuals.js";
|
|
2
|
+
import { readPage } from "../pages.js";
|
|
3
|
+
import { readVersion } from "../versions.js";
|
|
4
|
+
import { labelsFor } from "./labels.js";
|
|
5
|
+
const APPROVED = ["aprovado", "publicado"];
|
|
6
|
+
/**
|
|
7
|
+
* As páginas na ordem da árvore, com o texto que vai para o manual final: a versão marcada
|
|
8
|
+
* "No manual" quando existe, senão o texto atual. Página com problema fica de fora e vira aviso.
|
|
9
|
+
* A numeração conta só as páginas que entram, então o sumário não fica com buracos.
|
|
10
|
+
*/
|
|
11
|
+
export async function collectPages(dir, opts = {}) {
|
|
12
|
+
const meta = await readManual(dir);
|
|
13
|
+
const pages = [];
|
|
14
|
+
const warnings = [];
|
|
15
|
+
// `count` é compartilhado com os filhos de uma página que ficou de fora: eles sobem para o lugar dela.
|
|
16
|
+
const walk = async (nodes, prefix, depth, count) => {
|
|
17
|
+
for (const node of nodes) {
|
|
18
|
+
let included = false;
|
|
19
|
+
let number = prefix;
|
|
20
|
+
try {
|
|
21
|
+
const current = await readPage(dir, node.id);
|
|
22
|
+
if (!opts.onlyApproved || APPROVED.includes(current.status)) {
|
|
23
|
+
const source = node.published ? await readVersion(dir, node.id, node.published) : current;
|
|
24
|
+
count.n += 1;
|
|
25
|
+
number = prefix ? `${prefix}.${count.n}` : String(count.n);
|
|
26
|
+
pages.push({
|
|
27
|
+
id: node.id,
|
|
28
|
+
title: source.title || labelsFor(meta.language).untitled,
|
|
29
|
+
number,
|
|
30
|
+
depth,
|
|
31
|
+
fileName: node.file.split("/").pop() ?? node.file,
|
|
32
|
+
body: source.body,
|
|
33
|
+
});
|
|
34
|
+
included = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
warnings.push(`${node.file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
39
|
+
}
|
|
40
|
+
if (included)
|
|
41
|
+
await walk(node.children, number, depth + 1, { n: 0 });
|
|
42
|
+
else
|
|
43
|
+
await walk(node.children, prefix, depth, count);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
await walk(meta.pages, "", 0, { n: 0 });
|
|
47
|
+
return { meta, pages, warnings };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=collect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"collect.js","sourceRoot":"","sources":["../../../src/core/export/collect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAyBxC,MAAM,QAAQ,GAAiB,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,IAAI,GAAmB,EAAE;IACvE,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;IACnC,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,uGAAuG;IACvG,MAAM,IAAI,GAAG,KAAK,EAAE,KAAiB,EAAE,MAAc,EAAE,KAAa,EAAE,KAAoB,EAAE,EAAE;QAC5F,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,QAAQ,GAAG,KAAK,CAAC;YACrB,IAAI,MAAM,GAAG,MAAM,CAAC;YACpB,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;gBAC7C,IAAI,CAAC,IAAI,CAAC,YAAY,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;oBAC5D,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;oBAC1F,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;oBACb,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;oBAC3D,KAAK,CAAC,IAAI,CAAC;wBACT,EAAE,EAAE,IAAI,CAAC,EAAE;wBACX,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,QAAQ;wBACxD,MAAM;wBACN,KAAK;wBACL,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,IAAI;wBACjD,IAAI,EAAE,MAAM,CAAC,IAAI;qBAClB,CAAC,CAAC;oBACH,QAAQ,GAAG,IAAI,CAAC;gBAClB,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACrF,CAAC;YACD,IAAI,QAAQ;gBAAE,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;;gBAChE,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QACvD,CAAC;IACH,CAAC,CAAC;IACF,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACxC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC;AACnC,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Collected } from "./collect.js";
|
|
2
|
+
import { type ImageInfo } from "./refs.js";
|
|
3
|
+
/** Cor da marca, quando o manual não escolheu outra. */
|
|
4
|
+
export declare const DEFAULT_ACCENT = "#2b4fe0";
|
|
5
|
+
/** Um arquivo HTML com o manual inteiro: sumário ao lado, uma seção por página, marcações sobre os prints. */
|
|
6
|
+
export declare function renderHtml(collected: Collected, images: Map<string, ImageInfo>): string;
|