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
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PageNode } from "./types.js";
|
|
2
|
+
export interface Located {
|
|
3
|
+
node: PageNode;
|
|
4
|
+
/** O array que contém o nó, para inserir ou remover sem buscar de novo. */
|
|
5
|
+
siblings: PageNode[];
|
|
6
|
+
}
|
|
7
|
+
export declare function findNode(nodes: PageNode[], id: string): Located;
|
|
8
|
+
export declare function findIn(nodes: PageNode[], id: string): Located | undefined;
|
|
9
|
+
/** Lista de filhos onde inserir: os da página `parentId`, ou a raiz quando é null. */
|
|
10
|
+
export declare function childrenOf(nodes: PageNode[], parentId: string | null | undefined): PageNode[];
|
|
11
|
+
/** Percorre a árvore em ordem de leitura (pai antes dos filhos). */
|
|
12
|
+
export declare function flatten(nodes: PageNode[]): PageNode[];
|
|
13
|
+
export declare function clampIndex(index: number | undefined, length: number): number;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export function findNode(nodes, id) {
|
|
2
|
+
const found = findIn(nodes, id);
|
|
3
|
+
if (!found)
|
|
4
|
+
throw new Error(`Página não encontrada: ${id}`);
|
|
5
|
+
return found;
|
|
6
|
+
}
|
|
7
|
+
export function findIn(nodes, id) {
|
|
8
|
+
for (const node of nodes) {
|
|
9
|
+
if (node.id === id)
|
|
10
|
+
return { node, siblings: nodes };
|
|
11
|
+
const inner = findIn(node.children, id);
|
|
12
|
+
if (inner)
|
|
13
|
+
return inner;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
/** Lista de filhos onde inserir: os da página `parentId`, ou a raiz quando é null. */
|
|
18
|
+
export function childrenOf(nodes, parentId) {
|
|
19
|
+
return parentId ? findNode(nodes, parentId).node.children : nodes;
|
|
20
|
+
}
|
|
21
|
+
/** Percorre a árvore em ordem de leitura (pai antes dos filhos). */
|
|
22
|
+
export function flatten(nodes) {
|
|
23
|
+
return nodes.flatMap((n) => [n, ...flatten(n.children)]);
|
|
24
|
+
}
|
|
25
|
+
export function clampIndex(index, length) {
|
|
26
|
+
return index === undefined ? length : Math.max(0, Math.min(index, length));
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=tree.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.js","sourceRoot":"","sources":["../../src/core/tree.ts"],"names":[],"mappings":"AAQA,MAAM,UAAU,QAAQ,CAAC,KAAiB,EAAE,EAAU;IACpD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IAChC,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAC;IAC5D,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,KAAiB,EAAE,EAAU;IAClD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QACrD,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACxC,IAAI,KAAK;YAAE,OAAO,KAAK,CAAC;IAC1B,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,UAAU,CAAC,KAAiB,EAAE,QAAmC;IAC/E,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;AACpE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,OAAO,CAAC,KAAiB;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAyB,EAAE,MAAc;IAClE,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAC7E,CAAC"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/** Formato em disco de um manual. Ver docs/manual-format.md. */
|
|
2
|
+
export declare const FORMAT_VERSION = 1;
|
|
3
|
+
/** Nó da árvore de páginas guardada em manual.json. O título fica só no frontmatter da página. */
|
|
4
|
+
export interface PageNode {
|
|
5
|
+
id: string;
|
|
6
|
+
/** Caminho relativo à pasta do manual, sempre em `pages/`. */
|
|
7
|
+
file: string;
|
|
8
|
+
/** Versão escolhida para o manual final (arquivo em `.versoes/<id>/`). Sem ela, vale o texto atual. */
|
|
9
|
+
published?: string;
|
|
10
|
+
children: PageNode[];
|
|
11
|
+
}
|
|
12
|
+
export interface ManualMeta {
|
|
13
|
+
version: typeof FORMAT_VERSION;
|
|
14
|
+
title: string;
|
|
15
|
+
/** Tag BCP 47, como `pt-BR` ou `en-US`. */
|
|
16
|
+
language: string;
|
|
17
|
+
/** Aparência do manual exportado. Sem ela, vale a cor da marca. */
|
|
18
|
+
theme?: ManualTheme;
|
|
19
|
+
pages: PageNode[];
|
|
20
|
+
}
|
|
21
|
+
export interface ManualTheme {
|
|
22
|
+
/** Cor de destaque em hexadecimal (#2b4fe0): títulos, links e números dos passos no HTML e no PDF. */
|
|
23
|
+
accent?: string;
|
|
24
|
+
}
|
|
25
|
+
export interface Manual {
|
|
26
|
+
/** Caminho absoluto da pasta do manual. */
|
|
27
|
+
dir: string;
|
|
28
|
+
meta: ManualMeta;
|
|
29
|
+
}
|
|
30
|
+
/** Etapa da página no fluxo de aprovação. Sem status no arquivo, a página é rascunho. */
|
|
31
|
+
export declare const PAGE_STATUSES: readonly ["rascunho", "revisao", "aprovado", "publicado"];
|
|
32
|
+
export type PageStatus = (typeof PAGE_STATUSES)[number];
|
|
33
|
+
export interface Page {
|
|
34
|
+
id: string;
|
|
35
|
+
title: string;
|
|
36
|
+
status: PageStatus;
|
|
37
|
+
/** Conteúdo em Markdown, sem o frontmatter. */
|
|
38
|
+
body: string;
|
|
39
|
+
file: string;
|
|
40
|
+
}
|
|
41
|
+
/** Coordenadas normalizadas de 0 a 1, relativas à largura e à altura da imagem. */
|
|
42
|
+
export interface Point {
|
|
43
|
+
x: number;
|
|
44
|
+
y: number;
|
|
45
|
+
}
|
|
46
|
+
export interface Rect extends Point {
|
|
47
|
+
w: number;
|
|
48
|
+
h: number;
|
|
49
|
+
}
|
|
50
|
+
export interface Marker extends Point {
|
|
51
|
+
id: string;
|
|
52
|
+
/** Número exibido no marcador e na legenda. */
|
|
53
|
+
n: number;
|
|
54
|
+
label?: string;
|
|
55
|
+
}
|
|
56
|
+
export interface Highlight extends Rect {
|
|
57
|
+
id: string;
|
|
58
|
+
}
|
|
59
|
+
export interface Arrow {
|
|
60
|
+
id: string;
|
|
61
|
+
from: Point;
|
|
62
|
+
to: Point;
|
|
63
|
+
}
|
|
64
|
+
export type HotspotTarget = {
|
|
65
|
+
type: "page";
|
|
66
|
+
pageId: string;
|
|
67
|
+
} | {
|
|
68
|
+
type: "anchor";
|
|
69
|
+
pageId: string;
|
|
70
|
+
anchor: string;
|
|
71
|
+
} | {
|
|
72
|
+
type: "url";
|
|
73
|
+
href: string;
|
|
74
|
+
};
|
|
75
|
+
export interface Hotspot extends Rect {
|
|
76
|
+
id: string;
|
|
77
|
+
target: HotspotTarget;
|
|
78
|
+
}
|
|
79
|
+
/** Conteúdo do JSON que fica ao lado de cada imagem (`assets/foto.png.json`). */
|
|
80
|
+
export interface Annotations {
|
|
81
|
+
version: typeof FORMAT_VERSION;
|
|
82
|
+
markers: Marker[];
|
|
83
|
+
highlights: Highlight[];
|
|
84
|
+
arrows: Arrow[];
|
|
85
|
+
hotspots: Hotspot[];
|
|
86
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Formato em disco de um manual. Ver docs/manual-format.md. */
|
|
2
|
+
export const FORMAT_VERSION = 1;
|
|
3
|
+
/** Etapa da página no fluxo de aprovação. Sem status no arquivo, a página é rascunho. */
|
|
4
|
+
export const PAGE_STATUSES = ["rascunho", "revisao", "aprovado", "publicado"];
|
|
5
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,gEAAgE;AAEhE,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC;AAiChC,yFAAyF;AACzF,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAU,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Page } from "./types.js";
|
|
2
|
+
/** Versões guardadas de cada página: `.versoes/<id da página>/<data>.md`, uma cópia completa por versão. */
|
|
3
|
+
export declare const VERSIONS_DIR = ".versoes";
|
|
4
|
+
export interface VersionInfo {
|
|
5
|
+
id: string;
|
|
6
|
+
title: string;
|
|
7
|
+
/** Nome que a pessoa deu ao guardar ("Revisado pelo suporte"). Pode ser vazio. */
|
|
8
|
+
note: string;
|
|
9
|
+
savedAt: string;
|
|
10
|
+
/** É a versão escolhida para o manual final. */
|
|
11
|
+
published: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface Version extends VersionInfo {
|
|
14
|
+
body: string;
|
|
15
|
+
}
|
|
16
|
+
/** Guarda o texto atual da página como uma versão nova. */
|
|
17
|
+
export declare function saveVersion(dir: string, pageId: string, note?: string): Promise<VersionInfo>;
|
|
18
|
+
/** Versões da página, da mais recente para a mais antiga. */
|
|
19
|
+
export declare function listVersions(dir: string, pageId: string): Promise<VersionInfo[]>;
|
|
20
|
+
export declare function readVersion(dir: string, pageId: string, versionId: string): Promise<Version>;
|
|
21
|
+
/**
|
|
22
|
+
* Volta a página para o texto da versão. O texto que estava antes vira uma versão
|
|
23
|
+
* ("Antes de restaurar"), para restaurar nunca apagar nada.
|
|
24
|
+
*/
|
|
25
|
+
export declare function restoreVersion(dir: string, pageId: string, versionId: string): Promise<Page>;
|
|
26
|
+
/** Escolhe a versão que vai para o manual final. `null` volta a valer o texto atual da página. */
|
|
27
|
+
export declare function publishVersion(dir: string, pageId: string, versionId: string | null): Promise<void>;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { joinFrontmatter, splitFrontmatter } from "./frontmatter.js";
|
|
4
|
+
import { atomicWrite, exists, resolveInside, uniquePath } from "./fs.js";
|
|
5
|
+
import { readManual, updateManual } from "./manuals.js";
|
|
6
|
+
import { readPage, updatePage } from "./pages.js";
|
|
7
|
+
import { findNode } from "./tree.js";
|
|
8
|
+
/** Versões guardadas de cada página: `.versoes/<id da página>/<data>.md`, uma cópia completa por versão. */
|
|
9
|
+
export const VERSIONS_DIR = ".versoes";
|
|
10
|
+
const pageVersionsDir = (dir, pageId) => resolveInside(dir, path.posix.join(VERSIONS_DIR, pageId));
|
|
11
|
+
const versionFile = (dir, pageId, versionId) => resolveInside(pageVersionsDir(dir, pageId), `${versionId}.md`);
|
|
12
|
+
/** Guarda o texto atual da página como uma versão nova. */
|
|
13
|
+
export async function saveVersion(dir, pageId, note = "") {
|
|
14
|
+
const page = await readPage(dir, pageId);
|
|
15
|
+
const savedAt = new Date().toISOString();
|
|
16
|
+
const folder = pageVersionsDir(dir, pageId);
|
|
17
|
+
await mkdir(folder, { recursive: true });
|
|
18
|
+
// A data vira o nome do arquivo: a pasta fica em ordem e legível sem o Studio.
|
|
19
|
+
const file = await uniquePath(folder, savedAt.replace(/[:.]/g, "-"), ".md");
|
|
20
|
+
const clean = note.trim().slice(0, 200);
|
|
21
|
+
await atomicWrite(file, joinFrontmatter({ id: page.id, title: page.title, savedAt, note: clean }, page.body));
|
|
22
|
+
return { id: path.basename(file, ".md"), title: page.title, note: clean, savedAt, published: false };
|
|
23
|
+
}
|
|
24
|
+
/** Versões da página, da mais recente para a mais antiga. */
|
|
25
|
+
export async function listVersions(dir, pageId) {
|
|
26
|
+
const { node } = findNode((await readManual(dir)).pages, pageId);
|
|
27
|
+
const folder = pageVersionsDir(dir, pageId);
|
|
28
|
+
if (!(await exists(folder)))
|
|
29
|
+
return [];
|
|
30
|
+
const list = [];
|
|
31
|
+
for (const name of await readdir(folder)) {
|
|
32
|
+
if (!name.endsWith(".md"))
|
|
33
|
+
continue;
|
|
34
|
+
const { body: _, ...info } = await readVersionFile(path.join(folder, name), node.published);
|
|
35
|
+
list.push(info);
|
|
36
|
+
}
|
|
37
|
+
return list.sort((a, b) => b.savedAt.localeCompare(a.savedAt));
|
|
38
|
+
}
|
|
39
|
+
export async function readVersion(dir, pageId, versionId) {
|
|
40
|
+
const { node } = findNode((await readManual(dir)).pages, pageId);
|
|
41
|
+
return readVersionFile(versionFile(dir, pageId, versionId), node.published);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Volta a página para o texto da versão. O texto que estava antes vira uma versão
|
|
45
|
+
* ("Antes de restaurar"), para restaurar nunca apagar nada.
|
|
46
|
+
*/
|
|
47
|
+
export async function restoreVersion(dir, pageId, versionId) {
|
|
48
|
+
const version = await readVersion(dir, pageId, versionId);
|
|
49
|
+
const current = await readPage(dir, pageId);
|
|
50
|
+
if (current.title !== version.title || current.body !== version.body) {
|
|
51
|
+
await saveVersion(dir, pageId, "Antes de restaurar");
|
|
52
|
+
}
|
|
53
|
+
return updatePage(dir, pageId, { title: version.title, body: version.body });
|
|
54
|
+
}
|
|
55
|
+
/** Escolhe a versão que vai para o manual final. `null` volta a valer o texto atual da página. */
|
|
56
|
+
export async function publishVersion(dir, pageId, versionId) {
|
|
57
|
+
if (versionId !== null)
|
|
58
|
+
await readVersion(dir, pageId, versionId);
|
|
59
|
+
await updateManual(dir, (meta) => {
|
|
60
|
+
const { node } = findNode(meta.pages, pageId);
|
|
61
|
+
if (versionId === null)
|
|
62
|
+
delete node.published;
|
|
63
|
+
else
|
|
64
|
+
node.published = versionId;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async function readVersionFile(file, published) {
|
|
68
|
+
let text;
|
|
69
|
+
try {
|
|
70
|
+
text = await readFile(file, "utf8");
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
if (err.code === "ENOENT")
|
|
74
|
+
throw new Error(`Versão não encontrada: ${path.basename(file, ".md")}`);
|
|
75
|
+
throw err;
|
|
76
|
+
}
|
|
77
|
+
const { data, body } = splitFrontmatter(text);
|
|
78
|
+
const id = path.basename(file, ".md");
|
|
79
|
+
const str = (v) => (typeof v === "string" ? v : "");
|
|
80
|
+
return { id, title: str(data.title), note: str(data.note), savedAt: str(data.savedAt), published: id === published, body };
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=versions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"versions.js","sourceRoot":"","sources":["../../src/core/versions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
2
|
+
export declare class HttpError extends Error {
|
|
3
|
+
readonly status: number;
|
|
4
|
+
constructor(status: number, message: string);
|
|
5
|
+
}
|
|
6
|
+
/** O que um handler devolve. O servidor transforma em resposta HTTP. */
|
|
7
|
+
export type Reply = {
|
|
8
|
+
kind: "json";
|
|
9
|
+
status: number;
|
|
10
|
+
body: unknown;
|
|
11
|
+
} | {
|
|
12
|
+
kind: "file";
|
|
13
|
+
path: string;
|
|
14
|
+
cache: string;
|
|
15
|
+
};
|
|
16
|
+
export declare const json: (body: unknown, status?: number) => Reply;
|
|
17
|
+
export declare const file: (filePath: string, cache?: string) => Reply;
|
|
18
|
+
export declare function send(res: ServerResponse, reply: Reply): Promise<void>;
|
|
19
|
+
export declare function sendJson(res: ServerResponse, status: number, body: unknown): void;
|
|
20
|
+
export declare function readBody(req: IncomingMessage, limit: number): Promise<Uint8Array>;
|
|
21
|
+
export declare function readJson(req: IncomingMessage): Promise<Record<string, unknown>>;
|
|
22
|
+
/** Status HTTP para um erro vindo do core: "não encontrado" e arquivo inexistente viram 404, o resto 400. */
|
|
23
|
+
export declare function statusOf(err: unknown): number;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export class HttpError extends Error {
|
|
4
|
+
status;
|
|
5
|
+
constructor(status, message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.status = status;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export const json = (body, status = 200) => ({ kind: "json", status, body });
|
|
11
|
+
export const file = (filePath, cache = "no-cache") => ({ kind: "file", path: filePath, cache });
|
|
12
|
+
const MIME = {
|
|
13
|
+
".html": "text/html; charset=utf-8",
|
|
14
|
+
".js": "text/javascript; charset=utf-8",
|
|
15
|
+
".css": "text/css; charset=utf-8",
|
|
16
|
+
".json": "application/json; charset=utf-8",
|
|
17
|
+
".svg": "image/svg+xml",
|
|
18
|
+
".png": "image/png",
|
|
19
|
+
".jpg": "image/jpeg",
|
|
20
|
+
".jpeg": "image/jpeg",
|
|
21
|
+
".gif": "image/gif",
|
|
22
|
+
".webp": "image/webp",
|
|
23
|
+
".woff2": "font/woff2",
|
|
24
|
+
".pdf": "application/pdf",
|
|
25
|
+
".md": "text/markdown; charset=utf-8",
|
|
26
|
+
};
|
|
27
|
+
export async function send(res, reply) {
|
|
28
|
+
if (reply.kind === "json")
|
|
29
|
+
return sendJson(res, reply.status, reply.body);
|
|
30
|
+
const data = await readFile(reply.path);
|
|
31
|
+
res.writeHead(200, {
|
|
32
|
+
"content-type": MIME[path.extname(reply.path).toLowerCase()] ?? "application/octet-stream",
|
|
33
|
+
"cache-control": reply.cache,
|
|
34
|
+
"x-content-type-options": "nosniff",
|
|
35
|
+
});
|
|
36
|
+
res.end(data);
|
|
37
|
+
}
|
|
38
|
+
export function sendJson(res, status, body) {
|
|
39
|
+
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
|
|
40
|
+
res.end(JSON.stringify(body));
|
|
41
|
+
}
|
|
42
|
+
export async function readBody(req, limit) {
|
|
43
|
+
const chunks = [];
|
|
44
|
+
let size = 0;
|
|
45
|
+
for await (const chunk of req) {
|
|
46
|
+
size += chunk.length;
|
|
47
|
+
// Passou do limite: continua lendo sem guardar. Parar no meio derruba a conexão e o navegador
|
|
48
|
+
// nunca recebe o 413. shortcut: lê o envio inteiro; tudo bem só porque o servidor é local.
|
|
49
|
+
if (size <= limit)
|
|
50
|
+
chunks.push(chunk);
|
|
51
|
+
}
|
|
52
|
+
if (size > limit)
|
|
53
|
+
throw new HttpError(413, `Arquivo maior que ${limit / 1024 / 1024} MB.`);
|
|
54
|
+
return Buffer.concat(chunks);
|
|
55
|
+
}
|
|
56
|
+
export async function readJson(req) {
|
|
57
|
+
const text = Buffer.from(await readBody(req, 5 * 1024 * 1024)).toString("utf8");
|
|
58
|
+
let data;
|
|
59
|
+
try {
|
|
60
|
+
data = text ? JSON.parse(text) : {};
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new HttpError(400, "O corpo da requisição não é um JSON válido.");
|
|
64
|
+
}
|
|
65
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
66
|
+
throw new HttpError(400, "O corpo da requisição deve ser um objeto JSON.");
|
|
67
|
+
}
|
|
68
|
+
return data;
|
|
69
|
+
}
|
|
70
|
+
/** Status HTTP para um erro vindo do core: "não encontrado" e arquivo inexistente viram 404, o resto 400. */
|
|
71
|
+
export function statusOf(err) {
|
|
72
|
+
if (err instanceof HttpError)
|
|
73
|
+
return err.status;
|
|
74
|
+
const missing = (err instanceof Error && /não encontrad/i.test(err.message)) ||
|
|
75
|
+
(typeof err === "object" && err !== null && err.code === "ENOENT");
|
|
76
|
+
return missing ? 404 : 400;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/server/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
import { HttpError, send, sendJson, statusOf } from "./http.js";
|
|
3
|
+
import { matchRoute } from "./router.js";
|
|
4
|
+
import { assetRoutes } from "./routes/assets.js";
|
|
5
|
+
import { exportRoutes } from "./routes/export.js";
|
|
6
|
+
import { manualRoutes } from "./routes/manuals.js";
|
|
7
|
+
import { pageRoutes } from "./routes/pages.js";
|
|
8
|
+
import { versionRoutes } from "./routes/versions.js";
|
|
9
|
+
import { webRoutes } from "./routes/web.js";
|
|
10
|
+
import { assertLocalRequest } from "./security.js";
|
|
11
|
+
/** A ordem importa: a rota genérica do editor (`/*`) fica por último. */
|
|
12
|
+
const routes = [...manualRoutes, ...pageRoutes, ...versionRoutes, ...exportRoutes, ...assetRoutes, ...webRoutes];
|
|
13
|
+
export function createStudioServer(opts) {
|
|
14
|
+
return createServer(async (req, res) => {
|
|
15
|
+
try {
|
|
16
|
+
assertLocalRequest(req);
|
|
17
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
18
|
+
const segments = url.pathname.split("/").filter(Boolean).map(decodeURIComponent);
|
|
19
|
+
const method = req.method ?? "GET";
|
|
20
|
+
const match = matchRoute(routes, method, segments);
|
|
21
|
+
if (!match)
|
|
22
|
+
throw new HttpError(segments[0] === "api" ? 405 : 404, "Rota não encontrada.");
|
|
23
|
+
await send(res, await match.route.handle({ req, url, params: match.params, rest: match.rest, opts }));
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
sendJson(res, statusOf(err), { error: err instanceof Error ? err.message : String(err) });
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,WAAW,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,UAAU,EAAkC,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAInD,yEAAyE;AACzE,MAAM,MAAM,GAAY,CAAC,GAAG,YAAY,EAAE,GAAG,UAAU,EAAE,GAAG,aAAa,EAAE,GAAG,YAAY,EAAE,GAAG,WAAW,EAAE,GAAG,SAAS,CAAC,CAAC;AAE1H,MAAM,UAAU,kBAAkB,CAAC,IAAmB;IACpD,OAAO,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QACrC,IAAI,CAAC;YACH,kBAAkB,CAAC,GAAG,CAAC,CAAC;YACxB,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;YACxD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;YACjF,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;YACnC,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;YACnD,IAAI,CAAC,KAAK;gBAAE,MAAM,IAAI,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC;YAC3F,MAAM,IAAI,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QACxG,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC5F,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { IncomingMessage } from "node:http";
|
|
2
|
+
import type { Reply } from "./http.js";
|
|
3
|
+
export interface StudioOptions {
|
|
4
|
+
/** Pasta onde ficam os manuais. */
|
|
5
|
+
workspace: string;
|
|
6
|
+
/** Pasta com o build do editor (index.html e assets). Sem ela, só a API responde. */
|
|
7
|
+
webDir?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RouteContext {
|
|
10
|
+
req: IncomingMessage;
|
|
11
|
+
url: URL;
|
|
12
|
+
/** Segmentos `:nome` do padrão da rota, já decodificados. */
|
|
13
|
+
params: Record<string, string>;
|
|
14
|
+
/** O que veio depois de um `*` no fim do padrão. */
|
|
15
|
+
rest: string;
|
|
16
|
+
opts: StudioOptions;
|
|
17
|
+
}
|
|
18
|
+
export interface Route {
|
|
19
|
+
method: string;
|
|
20
|
+
/** Ex.: `/api/manuals/:slug/pages/:id`. Um `*` no fim aceita o resto do caminho. */
|
|
21
|
+
path: string;
|
|
22
|
+
handle: (ctx: RouteContext) => Promise<Reply> | Reply;
|
|
23
|
+
}
|
|
24
|
+
export type Match = {
|
|
25
|
+
route: Route;
|
|
26
|
+
params: Record<string, string>;
|
|
27
|
+
rest: string;
|
|
28
|
+
};
|
|
29
|
+
/** Primeira rota cujo método e padrão batem. A ordem da tabela define a prioridade. */
|
|
30
|
+
export declare function matchRoute(routes: Route[], method: string, segments: string[]): Match | null;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Primeira rota cujo método e padrão batem. A ordem da tabela define a prioridade. */
|
|
2
|
+
export function matchRoute(routes, method, segments) {
|
|
3
|
+
for (const route of routes) {
|
|
4
|
+
if (route.method !== method)
|
|
5
|
+
continue;
|
|
6
|
+
const match = matchPath(route.path, segments);
|
|
7
|
+
if (match)
|
|
8
|
+
return { route, ...match };
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function matchPath(pattern, segments) {
|
|
13
|
+
const parts = pattern.split("/").filter(Boolean);
|
|
14
|
+
const params = {};
|
|
15
|
+
for (const [i, part] of parts.entries()) {
|
|
16
|
+
if (part === "*")
|
|
17
|
+
return { params, rest: segments.slice(i).join("/") };
|
|
18
|
+
const seg = segments[i];
|
|
19
|
+
if (seg === undefined)
|
|
20
|
+
return null;
|
|
21
|
+
if (part.startsWith(":"))
|
|
22
|
+
params[part.slice(1)] = seg;
|
|
23
|
+
else if (part !== seg)
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return parts.length === segments.length ? { params, rest: "" } : null;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/server/router.ts"],"names":[],"mappings":"AA6BA,uFAAuF;AACvF,MAAM,UAAU,UAAU,CAAC,MAAe,EAAE,MAAc,EAAE,QAAkB;IAC5E,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;YAAE,SAAS;QACtC,MAAM,KAAK,GAAG,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC9C,IAAI,KAAK;YAAE,OAAO,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAAC;IACxC,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,SAAS,CAAC,OAAe,EAAE,QAAkB;IACpD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QACxC,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvE,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;QACxB,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACnC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;aACjD,IAAI,IAAI,KAAK,GAAG;YAAE,OAAO,IAAI,CAAC;IACrC,CAAC;IACD,OAAO,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { cleanUnusedAssets, findUnusedAssets, readAnnotations, resolveInside, saveAsset, writeAnnotations, } from "../../core/index.js";
|
|
2
|
+
import { file, json, readBody, readJson } from "../http.js";
|
|
3
|
+
import { manualDir } from "../workspace.js";
|
|
4
|
+
const MAX_UPLOAD = 20 * 1024 * 1024;
|
|
5
|
+
export const assetRoutes = [
|
|
6
|
+
{
|
|
7
|
+
method: "POST",
|
|
8
|
+
path: "/api/manuals/:slug/assets",
|
|
9
|
+
handle: async (ctx) => {
|
|
10
|
+
const name = ctx.url.searchParams.get("name") ?? "imagem.png";
|
|
11
|
+
const data = await readBody(ctx.req, MAX_UPLOAD);
|
|
12
|
+
return json({ path: await saveAsset(manualDir(ctx), name, data) }, 201);
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
method: "GET",
|
|
17
|
+
path: "/api/manuals/:slug/annotations",
|
|
18
|
+
handle: async (ctx) => json(await readAnnotations(manualDir(ctx), ctx.url.searchParams.get("asset") ?? "")),
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
method: "PUT",
|
|
22
|
+
path: "/api/manuals/:slug/annotations",
|
|
23
|
+
handle: async (ctx) => json(await writeAnnotations(manualDir(ctx), ctx.url.searchParams.get("asset") ?? "", await readJson(ctx.req))),
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
// Imagens e marcações que nenhuma página, versão ou item da lixeira usa.
|
|
27
|
+
method: "GET",
|
|
28
|
+
path: "/api/manuals/:slug/assets/unused",
|
|
29
|
+
handle: async (ctx) => json({ count: (await findUnusedAssets(manualDir(ctx))).length }),
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
// Move as sem uso para .lixeira/imagens-<data>/. Nada é apagado.
|
|
33
|
+
method: "POST",
|
|
34
|
+
path: "/api/manuals/:slug/assets/clean",
|
|
35
|
+
handle: async (ctx) => json(await cleanUnusedAssets(manualDir(ctx))),
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
// Imagens do manual, usadas pelo editor: /files/<manual>/assets/<arquivo>
|
|
39
|
+
method: "GET",
|
|
40
|
+
path: "/files/:slug/*",
|
|
41
|
+
handle: (ctx) => file(resolveInside(manualDir(ctx), ctx.rest)),
|
|
42
|
+
},
|
|
43
|
+
];
|
|
44
|
+
//# sourceMappingURL=assets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../src/server/routes/assets.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,SAAS,EACT,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,GAAG,IAAI,CAAC;AAEpC,MAAM,CAAC,MAAM,WAAW,GAAY;IAClC;QACE,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,2BAA2B;QACjC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpB,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC;YAC9D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YACjD,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QAC1E,CAAC;KACF;IACD;QACE,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,gCAAgC;QACtC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,eAAe,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;KAC5G;IACD;QACE,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,gCAAgC;QACtC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CACpB,IAAI,CAAC,MAAM,gBAAgB,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;KACjH;IACD;QACE,yEAAyE;QACzE,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,kCAAkC;QACxC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,MAAM,gBAAgB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;KACxF;IACD;QACE,iEAAiE;QACjE,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,iCAAiC;QACvC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,iBAAiB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;KACrE;IACD;QACE,0EAA0E;QAC1E,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,gBAAgB;QACtB,MAAM,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;KAC/D;CACF,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EXPORT_FORMATS, exportManual, setManualTheme } from "../../core/index.js";
|
|
2
|
+
import { HttpError, json, readJson } from "../http.js";
|
|
3
|
+
import { manualDir, optionalString } from "../workspace.js";
|
|
4
|
+
export const exportRoutes = [
|
|
5
|
+
{
|
|
6
|
+
// { format: "html" | "pdf" | "markdown", onlyApproved?: true }. Devolve o arquivo gerado e o endereço para abrir no navegador.
|
|
7
|
+
method: "POST",
|
|
8
|
+
path: "/api/manuals/:slug/export",
|
|
9
|
+
handle: async (ctx) => {
|
|
10
|
+
const body = await readJson(ctx.req);
|
|
11
|
+
const format = optionalString(body.format);
|
|
12
|
+
if (!format || !EXPORT_FORMATS.includes(format)) {
|
|
13
|
+
throw new HttpError(400, `Formato de exportação inválido. Use ${EXPORT_FORMATS.join(", ")}.`);
|
|
14
|
+
}
|
|
15
|
+
const result = await exportManual(manualDir(ctx), format, { onlyApproved: body.onlyApproved === true });
|
|
16
|
+
return json({ ...result, url: `/files/${encodeURIComponent(ctx.params.slug ?? "")}/${result.file}` });
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
// { accent: "#2b4fe0" } escolhe a cor do manual exportado; { accent: null } volta à da marca.
|
|
21
|
+
method: "PUT",
|
|
22
|
+
path: "/api/manuals/:slug/theme",
|
|
23
|
+
handle: async (ctx) => {
|
|
24
|
+
const body = await readJson(ctx.req);
|
|
25
|
+
const accent = body.accent === null ? undefined : optionalString(body.accent);
|
|
26
|
+
if (accent === undefined && body.accent !== null)
|
|
27
|
+
throw new HttpError(400, "Informe accent (texto ou null).");
|
|
28
|
+
await setManualTheme(manualDir(ctx), accent ? { accent } : {});
|
|
29
|
+
return json({ ok: true });
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
//# sourceMappingURL=export.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"export.js","sourceRoot":"","sources":["../../../src/server/routes/export.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAqB,YAAY,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEvD,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE5D,MAAM,CAAC,MAAM,YAAY,GAAY;IACnC;QACE,+HAA+H;QAC/H,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,2BAA2B;QACjC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAA6B,CAAC;YACvE,IAAI,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;gBAChD,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,uCAAuC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAChG,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,KAAK,IAAI,EAAE,CAAC,CAAC;YACxG,OAAO,IAAI,CAAC,EAAE,GAAG,MAAM,EAAE,GAAG,EAAE,UAAU,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACxG,CAAC;KACF;IACD;QACE,8FAA8F;QAC9F,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,0BAA0B;QAChC,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9E,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI;gBAAE,MAAM,IAAI,SAAS,CAAC,GAAG,EAAE,iCAAiC,CAAC,CAAC;YAC9G,MAAM,cAAc,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC/D,OAAO,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC;KACF;CACF,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { createManual, listManuals, readManual, readPageTree } from "../../core/index.js";
|
|
3
|
+
import { json, readJson } from "../http.js";
|
|
4
|
+
import { manualDir, optionalString } from "../workspace.js";
|
|
5
|
+
const summary = (m) => ({ slug: path.basename(m.dir), title: m.meta.title, language: m.meta.language });
|
|
6
|
+
export const manualRoutes = [
|
|
7
|
+
{
|
|
8
|
+
method: "GET",
|
|
9
|
+
path: "/api/manuals",
|
|
10
|
+
handle: async ({ opts }) => json((await listManuals(opts.workspace)).map(summary)),
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
method: "POST",
|
|
14
|
+
path: "/api/manuals",
|
|
15
|
+
handle: async ({ opts, req }) => {
|
|
16
|
+
const body = await readJson(req);
|
|
17
|
+
const manual = await createManual(opts.workspace, {
|
|
18
|
+
title: optionalString(body.title) ?? "",
|
|
19
|
+
language: optionalString(body.language) ?? "pt-BR",
|
|
20
|
+
});
|
|
21
|
+
return json(summary(manual), 201);
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
method: "GET",
|
|
26
|
+
path: "/api/manuals/:slug",
|
|
27
|
+
handle: async (ctx) => {
|
|
28
|
+
const dir = manualDir(ctx);
|
|
29
|
+
const meta = await readManual(dir);
|
|
30
|
+
return json({
|
|
31
|
+
slug: ctx.params.slug,
|
|
32
|
+
title: meta.title,
|
|
33
|
+
language: meta.language,
|
|
34
|
+
theme: meta.theme ?? {},
|
|
35
|
+
pages: await readPageTree(dir),
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
//# sourceMappingURL=manuals.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manuals.js","sourceRoot":"","sources":["../../../src/server/routes/manuals.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAe,UAAU,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACvG,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAE5D,MAAM,OAAO,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;AAEhH,MAAM,CAAC,MAAM,YAAY,GAAY;IACnC;QACE,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;KACnF;IACD;QACE,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE;YAC9B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC;YACjC,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE;gBAChD,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;gBACvC,QAAQ,EAAE,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,OAAO;aACnD,CAAC,CAAC;YACH,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;QACpC,CAAC;KACF;IACD;QACE,MAAM,EAAE,KAAK;QACb,IAAI,EAAE,oBAAoB;QAC1B,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YACpB,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;YAC3B,MAAM,IAAI,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;YACnC,OAAO,IAAI,CAAC;gBACV,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI;gBACrB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,EAAE;gBACvB,KAAK,EAAE,MAAM,YAAY,CAAC,GAAG,CAAC;aAC/B,CAAC,CAAC;QACL,CAAC;KACF;CACF,CAAC"}
|