Add multi-language syntax highlighting to source editor, improve copy button to copy raw file content, and add permission error handling to file API endpoint

This commit is contained in:
Bruno Charest 2026-03-22 23:52:10 -04:00
parent 6f694148db
commit 1129d1bca5
3 changed files with 76 additions and 9 deletions

View File

@ -527,7 +527,14 @@ async def api_file(vault_name: str, path: str = Query(..., description="Relative
if not file_path.exists() or not file_path.is_file(): if not file_path.exists() or not file_path.is_file():
raise HTTPException(status_code=404, detail=f"File not found: {path}") raise HTTPException(status_code=404, detail=f"File not found: {path}")
try:
raw = file_path.read_text(encoding="utf-8", errors="replace") raw = file_path.read_text(encoding="utf-8", errors="replace")
except PermissionError:
raise HTTPException(status_code=403, detail=f"Permission denied: cannot read file {path}")
except Exception as e:
logger.error(f"Error reading file {path}: {e}")
raise HTTPException(status_code=500, detail=f"Error reading file: {str(e)}")
ext = file_path.suffix.lower() ext = file_path.suffix.lower()
if ext == ".md": if ext == ".md":

View File

@ -1203,16 +1203,25 @@
}); });
// Action buttons // Action buttons
const copyBtn = el("button", { class: "btn-action", title: "Copier le chemin" }, [ const copyBtn = el("button", { class: "btn-action", title: "Copier la source" }, [
icon("copy", 14), icon("copy", 14),
document.createTextNode("Copier"), document.createTextNode("Copier"),
]); ]);
copyBtn.addEventListener("click", () => { copyBtn.addEventListener("click", async () => {
navigator.clipboard.writeText(`${data.vault}/${data.path}`).then(() => { try {
copyBtn.querySelector("span") || (copyBtn.lastChild.textContent = "Copié !"); // Fetch raw content if not already cached
if (!cachedRawSource) {
const rawUrl = `/api/file/${encodeURIComponent(data.vault)}/raw?path=${encodeURIComponent(data.path)}`;
const rawData = await api(rawUrl);
cachedRawSource = rawData.raw;
}
await navigator.clipboard.writeText(cachedRawSource);
copyBtn.lastChild.textContent = "Copié !"; copyBtn.lastChild.textContent = "Copié !";
setTimeout(() => (copyBtn.lastChild.textContent = "Copier"), 1500); setTimeout(() => (copyBtn.lastChild.textContent = "Copier"), 1500);
}); } catch (err) {
console.error("Copy error:", err);
showToast("Erreur lors de la copie");
}
}); });
const sourceBtn = el("button", { class: "btn-action", title: "Voir la source" }, [ const sourceBtn = el("button", { class: "btn-action", title: "Voir la source" }, [
@ -1903,12 +1912,13 @@
try { try {
await waitForCodeMirror(); await waitForCodeMirror();
const { EditorView, EditorState, basicSetup, markdown, oneDark, keymap } = window.CodeMirror; const { EditorView, EditorState, basicSetup, markdown, python, javascript, html, css, json, xml, sql, php, cpp, java, rust, oneDark, keymap } = window.CodeMirror;
const currentTheme = document.documentElement.getAttribute("data-theme"); const currentTheme = document.documentElement.getAttribute("data-theme");
const fileExt = filePath.split(".").pop().toLowerCase();
const extensions = [ const extensions = [
basicSetup, basicSetup,
markdown(),
keymap.of([{ keymap.of([{
key: "Mod-s", key: "Mod-s",
run: () => { run: () => {
@ -1919,6 +1929,45 @@
EditorView.lineWrapping, EditorView.lineWrapping,
]; ];
// Add language support based on file extension
const langMap = {
"md": markdown,
"markdown": markdown,
"py": python,
"js": javascript,
"jsx": javascript,
"ts": javascript,
"tsx": javascript,
"mjs": javascript,
"cjs": javascript,
"html": html,
"htm": html,
"css": css,
"scss": css,
"less": css,
"json": json,
"xml": xml,
"svg": xml,
"sql": sql,
"php": php,
"cpp": cpp,
"cc": cpp,
"cxx": cpp,
"c": cpp,
"h": cpp,
"hpp": cpp,
"java": java,
"rs": rust,
"sh": javascript, // Using javascript for shell scripts as fallback
"bash": javascript,
"zsh": javascript,
};
const langMode = langMap[fileExt];
if (langMode) {
extensions.push(langMode());
}
if (currentTheme === "dark") { if (currentTheme === "dark") {
extensions.push(oneDark); extensions.push(oneDark);
} }

View File

@ -26,6 +26,17 @@
import { searchKeymap, highlightSelectionMatches } from "https://esm.sh/@codemirror/search@6.3.0?external=@codemirror/state"; import { searchKeymap, highlightSelectionMatches } from "https://esm.sh/@codemirror/search@6.3.0?external=@codemirror/state";
import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from "https://esm.sh/@codemirror/autocomplete@6.5.0?external=@codemirror/state"; import { autocompletion, completionKeymap, closeBrackets, closeBracketsKeymap } from "https://esm.sh/@codemirror/autocomplete@6.5.0?external=@codemirror/state";
import { markdown } from "https://esm.sh/@codemirror/lang-markdown@6.1.0?external=@codemirror/state"; import { markdown } from "https://esm.sh/@codemirror/lang-markdown@6.1.0?external=@codemirror/state";
import { python } from "https://esm.sh/@codemirror/lang-python@6.1.3?external=@codemirror/state";
import { javascript } from "https://esm.sh/@codemirror/lang-javascript@6.1.7?external=@codemirror/state";
import { html } from "https://esm.sh/@codemirror/lang-html@6.4.3?external=@codemirror/state";
import { css } from "https://esm.sh/@codemirror/lang-css@6.2.0?external=@codemirror/state";
import { json } from "https://esm.sh/@codemirror/lang-json@6.0.1?external=@codemirror/state";
import { xml } from "https://esm.sh/@codemirror/lang-xml@6.0.2?external=@codemirror/state";
import { sql } from "https://esm.sh/@codemirror/lang-sql@6.5.0?external=@codemirror/state";
import { php } from "https://esm.sh/@codemirror/lang-php@6.0.1?external=@codemirror/state";
import { cpp } from "https://esm.sh/@codemirror/lang-cpp@6.0.2?external=@codemirror/state";
import { java } from "https://esm.sh/@codemirror/lang-java@6.0.1?external=@codemirror/state";
import { rust } from "https://esm.sh/@codemirror/lang-rust@6.0.1?external=@codemirror/state";
import { oneDark } from "https://esm.sh/@codemirror/theme-one-dark@6.1.0?external=@codemirror/state"; import { oneDark } from "https://esm.sh/@codemirror/theme-one-dark@6.1.0?external=@codemirror/state";
const basicSetup = [ const basicSetup = [
@ -56,7 +67,7 @@
]) ])
]; ];
window.CodeMirror = { EditorView, EditorState, basicSetup, markdown, oneDark, keymap }; window.CodeMirror = { EditorView, EditorState, basicSetup, markdown, python, javascript, html, css, json, xml, sql, php, cpp, java, rust, oneDark, keymap };
</script> </script>
</head> </head>
<body> <body>