feat(docs): export database documentation as standalone HTML

This commit is contained in:
Fernando Possebon authored and GitHub committed 2026-08-08 11:04:16 +08:00
1 parent efd0c381ad
commit 63498da121
59 files changed
+4374 -237

No files matched your search

@@ -1,7 +1,7 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { Network } from "@lucide/vue";
import { Download, Network } from "@lucide/vue";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import DocsApp from "@/docs/DocsApp.vue";
@@ -9,9 +9,14 @@ import { emptyAnnotations, removeGroup, setColumnNote, setProjectNote, setTableG
import type { AnnotationFile, DocsEdit, SchemaSnapshot } from "@/docs/types";
import type { Translate } from "@/docs/docsWarnings";
import * as api from "@/lib/backend/api";
import { isTauriRuntime } from "@/lib/backend/tauriRuntime";
import { useConnectionStore } from "@/stores/connectionStore";
import { createAutosave } from "./docsAutosave";
// The languages `to_standalone_html` accepts. Kept in sync manually — this
// component lives outside `src/docs/` and cannot import from dbx-core.
const EXPORT_LANGUAGES = ["en", "es", "it", "ja", "ko", "pt-BR", "zh-CN", "zh-TW"];
const props = defineProps<{
prefillConnectionId?: string;
prefillDatabase?: string;
@@ -23,7 +28,7 @@ const open = defineModel<boolean>("open", { default: false });
// This component lives OUTSIDE src/docs/, so it may and must use useI18n():
// it is what supplies the `translate` prop that the viewer components need,
// since they are banned from importing vue-i18n themselves.
const { t } = useI18n();
const { t, locale } = useI18n();
// `Translate` is one narrow signature; vue-i18n's `t` is heavily overloaded and
// does not assign to it directly, so bridge it explicitly.
@@ -70,6 +75,9 @@ const statusLabel = computed(() => {
const canOpenDiagram = computed(() => (props.prefillConnectionId ?? "") !== "" && (props.prefillDatabase ?? "") !== "");
const exporting = ref(false);
const exportError = ref<string | null>(null);
async function load(): Promise<void> {
const connectionId = props.prefillConnectionId;
const database = props.prefillDatabase;
@@ -153,6 +161,32 @@ function openDiagram(): void {
connectionStore.diagramSource = { connectionId, database, schema: props.prefillSchema };
}
async function exportHtml(): Promise<void> {
if (!snapshot.value) return;
exporting.value = true;
exportError.value = null;
try {
let outputPath = `${snapshot.value.project.name}-docs.html`;
if (isTauriRuntime()) {
const { save } = await import("@tauri-apps/plugin-dialog");
const chosen = await save({ defaultPath: outputPath, filters: [{ name: "HTML", extensions: ["html"] }] });
if (!chosen) return; // the user cancelled; not an error
outputPath = chosen as string;
}
// `to_standalone_html` rejects any language outside its fixed list; the
// app's locale is otherwise a superset risk, so fall back rather than
// surface that rejection to the user.
const lang = EXPORT_LANGUAGES.includes(locale.value) ? locale.value : "en";
await api.exportDocsHtml(outputPath, snapshot.value, annotations.value, lang);
} catch (error) {
// Never swallowed: a failed export that reports success is the worst
// outcome here, exactly as with a failed autosave.
exportError.value = t("docs.exportFailed", { error: String(error) });
} finally {
exporting.value = false;
}
}
watch(
open,
(isOpen, wasOpen) => {
@@ -179,10 +213,15 @@ watch(
<span v-if="statusLabel" class="text-xs font-normal" :class="status.state === 'failed' ? 'text-destructive' : 'text-muted-foreground'">
{{ statusLabel }}
</span>
<span v-if="exportError" class="text-xs font-normal text-destructive">{{ exportError }}</span>
<Button v-if="canOpenDiagram" variant="outline" size="sm" class="ml-auto" @click="openDiagram()">
<Network class="w-4 h-4" />
{{ t("docs.openDiagram") }}
</Button>
<Button v-if="snapshot" variant="outline" size="sm" :disabled="exporting" @click="exportHtml()">
<Download class="w-4 h-4" />
{{ exporting ? t("docs.exporting") : t("docs.exportHtml") }}
</Button>
</DialogTitle>
</DialogHeader>