fix: /local-workspace — chemin du header + clic sur un dossier du sidebar (v7.69.9)
Corrige deux régressions de /local-workspace :
- le chemin du header restait bloqué sur « Home / <workspace> » quel que
soit le dossier affiché : la route rend désormais breadcrumb_items
(Home / <workspace> / <dossier> / <sous-dossier>, niveaux cliquables,
collapse « … » au-delà de 4) et la navigation sans rechargement recalcule
le chemin via l'event flowdeck:breadcrumb-changed ;
- le clic sur un dossier du sidebar affichait TOUS les composants à la
fois : Alpine.data('wsInitData') retournait le même objet singleton, le
2e montage (navigation partielle) levait « Cannot redefine property:
\ » et initTree abandonnait, laissant tout le contenu au state
brut. La factory retourne désormais une enveloppe fraîche par montage
qui délègue à l'état réactif partagé. #lw-config est aussi relu à chaque
exécution (le 2e montage gardait le folder_id du 1er chargement).
Inclus également le travail en cours de l'arbre : Library (colonnes Last
visited/Source, ordre d'en-tête, favoris à icônes Workspace), Meeting
Notes (bloc, CSS, routes, docs), coloration de code hljs, badges
favori/publié dans l'arbre local-workspace, docs (DATA_MODEL,
architectures) et tests associés.
This commit is contained in:
+224
-23
@@ -66,28 +66,226 @@ def _block_text(b: dict) -> str:
|
||||
# ── Source resolution: read real textual content for ANY page type ──
|
||||
|
||||
# Extensions whose content is plain text / code / markdown (textual exportable).
|
||||
# Mirrors static/js/code-highlight.js EXT_TO_LANG (single source of truth for
|
||||
# the viewer lives client-side; this set gates server-side text handling:
|
||||
# file viewer, copy-to-clipboard, exports, imports).
|
||||
_TEXTUAL_EXTS = {
|
||||
"md", "markdown", "txt", "log", "text",
|
||||
"py", "js", "ts", "jsx", "tsx", "html", "htm", "css", "json", "xml",
|
||||
"yaml", "yml", "toml", "ini", "cfg", "conf", "env", "sh", "bash", "zsh",
|
||||
"ps1", "bat", "cmd", "rb", "go", "rs", "java", "c", "cpp", "h", "hpp",
|
||||
"php", "swift", "kt", "scala", "sql", "r", "vue", "svelte", "astro",
|
||||
"properties", "gitignore", "dockerfile", "makefile",
|
||||
"md", "markdown", "mkd", "mdown", "mdwn", "rmd", "qmd",
|
||||
"txt", "log", "text", "csv", "tsv", "tab", "nfo", "asc",
|
||||
"py", "pyw", "pyi", "gyp", "gypi",
|
||||
"js", "mjs", "cjs", "jsx", "ts", "mts", "cts", "tsx",
|
||||
"html", "htm", "xhtml", "xht", "xml", "xsd", "xsl", "xslt",
|
||||
"rss", "atom", "plist", "wsf", "svg", "axml",
|
||||
"vue", "svelte", "astro",
|
||||
"css", "scss", "sass", "less",
|
||||
"json", "json5", "jsonc", "geojson", "webmanifest", "har", "avsc",
|
||||
"yaml", "yml", "toml", "ini", "cfg", "conf", "env", "properties",
|
||||
"inf", "reg", "url", "prop",
|
||||
"gitconfig", "gitmodules", "editorconfig",
|
||||
"sh", "bash", "zsh", "ksh", "mksh", "yash", "fish",
|
||||
"ps1", "psm1", "psd1", "bat", "cmd",
|
||||
"rb", "gemspec", "rake", "rbw",
|
||||
"go", "rs", "java", "jsp", "kt", "kts",
|
||||
"scala", "sc", "groovy", "gvy", "gradle",
|
||||
"c", "cpp", "cc", "cxx", "c++", "hh", "hxx", "hcc", "h", "hpp",
|
||||
"cs", "csx", "cake", "ino", "pde",
|
||||
"php", "phtml", "swift", "sql", "pgsql", "psql", "mysql", "mssql",
|
||||
"r", "jl", "lua", "dart",
|
||||
"pl", "pm", "pod",
|
||||
"hs", "lhs", "ex", "exs", "eex", "heex", "leex",
|
||||
"erl", "hrl", "escript",
|
||||
"clj", "cljs", "cljc", "edn",
|
||||
"ml", "mli", "fs", "fsi", "fsx", "fsscript",
|
||||
"nim", "nims", "d", "di",
|
||||
"m", "mm", "vb", "vbs", "bas", "pas", "pp", "dpr",
|
||||
"vhd", "vhdl", "v", "vh", "sv", "svh",
|
||||
"asm", "s", "nasm", "inc",
|
||||
"ada", "ads", "adb",
|
||||
"f", "for", "f90", "f95", "f03", "f08",
|
||||
"cob", "cbl", "cpy",
|
||||
"dockerfile", "containerfile", "tf", "tfvars", "hcl",
|
||||
"cmake", "makefile", "mk", "mak", "ninja",
|
||||
"gql", "graphql", "graphqls", "proto", "thrift",
|
||||
"vim", "vimrc", "gvimrc", "awk", "gcode", "gco", "nc", "ngc",
|
||||
"http", "rest", "nginx", "apache", "htaccess", "wat",
|
||||
"diff", "patch", "rej",
|
||||
"tex", "latex", "sty", "cls", "dtx", "bib", "bbl",
|
||||
"gitignore", "gitattributes", "gitkeep", "dockerignore", "npmignore",
|
||||
"lock", "mod", "sum",
|
||||
}
|
||||
# Well-known extensionless / dotfile basenames that are textual code.
|
||||
_TEXTUAL_FILENAMES = {
|
||||
"dockerfile", "containerfile", "makefile", "gnumakefile",
|
||||
"cmakelists.txt", "rakefile", "gemfile", "vagrantfile",
|
||||
"berksfile", "brewfile", "dangerfile", "fastfile", "podfile",
|
||||
"go.mod", "go.sum",
|
||||
".bashrc", ".bash_profile", ".bash_login", ".bash_logout",
|
||||
".zshrc", ".zshenv", ".profile", ".shrc", ".kshrc",
|
||||
".vimrc", ".gvimrc", ".viminfo",
|
||||
".gitconfig", ".editorconfig", ".htaccess",
|
||||
}
|
||||
# Exact basenames → highlight.js id (checked before the extension).
|
||||
_FILENAME_LANG = {
|
||||
"dockerfile": "dockerfile", "containerfile": "dockerfile",
|
||||
"makefile": "makefile", "gnumakefile": "makefile",
|
||||
"cmakelists.txt": "cmake",
|
||||
"rakefile": "ruby", "gemfile": "ruby", "vagrantfile": "ruby",
|
||||
"berksfile": "ruby", "brewfile": "ruby", "dangerfile": "ruby",
|
||||
"fastfile": "ruby", "podfile": "ruby",
|
||||
"go.mod": "go",
|
||||
"nginx.conf": "nginx",
|
||||
".bashrc": "bash", ".bash_profile": "bash", ".bash_login": "bash",
|
||||
".bash_logout": "bash", ".zshrc": "bash", ".zshenv": "bash",
|
||||
".profile": "bash", ".shrc": "bash", ".kshrc": "bash",
|
||||
".vimrc": "vim", ".gvimrc": "vim",
|
||||
".gitconfig": "ini", ".editorconfig": "ini", ".htaccess": "apache",
|
||||
}
|
||||
_CODE_LANG = {
|
||||
"py": "python", "js": "javascript", "ts": "typescript", "jsx": "javascript",
|
||||
"tsx": "typescript", "html": "html", "htm": "html", "css": "css",
|
||||
"json": "json", "xml": "xml", "yaml": "yaml", "yml": "yaml",
|
||||
"toml": "toml", "ini": "ini", "cfg": "ini", "conf": "ini", "env": "ini",
|
||||
"sh": "bash", "bash": "bash", "zsh": "bash", "ps1": "powershell",
|
||||
"bat": "batch", "cmd": "batch", "rb": "ruby", "go": "go", "rs": "rust",
|
||||
"java": "java", "c": "c", "cpp": "cpp", "h": "c", "hpp": "cpp",
|
||||
"php": "php", "swift": "swift", "kt": "kotlin", "scala": "scala",
|
||||
"sql": "sql", "r": "r", "vue": "html", "svelte": "html",
|
||||
"astro": "html", "properties": "ini", "md": "markdown",
|
||||
"markdown": "markdown", "txt": "plaintext", "log": "plaintext",
|
||||
"text": "plaintext",
|
||||
"py": "python", "pyw": "python", "pyi": "python",
|
||||
"gyp": "python", "gypi": "python",
|
||||
"js": "javascript", "mjs": "javascript", "cjs": "javascript",
|
||||
"jsx": "javascript",
|
||||
"ts": "typescript", "mts": "typescript", "cts": "typescript",
|
||||
"tsx": "typescript",
|
||||
"html": "xml", "htm": "xml", "xhtml": "xml", "xht": "xml",
|
||||
"xml": "xml", "xsd": "xml", "xsl": "xml", "xslt": "xml",
|
||||
"rss": "xml", "atom": "xml", "plist": "xml", "wsf": "xml",
|
||||
"svg": "xml", "axml": "xml",
|
||||
"vue": "xml", "svelte": "xml", "astro": "xml",
|
||||
"css": "css", "scss": "scss", "sass": "scss", "less": "less",
|
||||
"json": "json", "json5": "json", "jsonc": "json",
|
||||
"geojson": "json", "webmanifest": "json", "har": "json", "avsc": "json",
|
||||
"yaml": "yaml", "yml": "yaml",
|
||||
"toml": "ini", "ini": "ini", "cfg": "ini", "conf": "ini",
|
||||
"env": "ini", "properties": "ini", "prop": "ini",
|
||||
"inf": "ini", "reg": "ini", "url": "ini",
|
||||
"gitconfig": "ini", "gitmodules": "ini", "editorconfig": "ini",
|
||||
"ninja": "ini",
|
||||
"md": "markdown", "markdown": "markdown", "mkd": "markdown",
|
||||
"mdown": "markdown", "mdwn": "markdown", "rmd": "markdown",
|
||||
"qmd": "markdown",
|
||||
"tex": "latex", "latex": "latex", "sty": "latex", "cls": "latex",
|
||||
"dtx": "latex",
|
||||
"sql": "sql", "pgsql": "sql", "psql": "sql", "mysql": "sql",
|
||||
"mssql": "sql",
|
||||
"sh": "bash", "bash": "bash", "zsh": "bash", "ksh": "bash",
|
||||
"mksh": "bash", "yash": "bash",
|
||||
"fish": "shell",
|
||||
"ps1": "powershell", "psm1": "powershell", "psd1": "powershell",
|
||||
"bat": "dos", "cmd": "dos",
|
||||
"rb": "ruby", "gemspec": "ruby", "rake": "ruby", "rbw": "ruby",
|
||||
"go": "go", "rs": "rust",
|
||||
"java": "java", "jsp": "java",
|
||||
"kt": "kotlin", "kts": "kotlin",
|
||||
"scala": "scala", "sc": "scala",
|
||||
"groovy": "groovy", "gvy": "groovy", "gradle": "groovy",
|
||||
"c": "c", "h": "c",
|
||||
"cpp": "cpp", "cc": "cpp", "cxx": "cpp", "c++": "cpp",
|
||||
"hh": "cpp", "hxx": "cpp", "hcc": "cpp", "hpp": "cpp",
|
||||
"ino": "cpp", "pde": "cpp",
|
||||
"cs": "csharp", "csx": "csharp", "cake": "csharp",
|
||||
"php": "php", "phtml": "php-template",
|
||||
"swift": "swift", "r": "r", "jl": "julia", "lua": "lua",
|
||||
"dart": "dart", "pl": "perl", "pm": "perl", "pod": "perl",
|
||||
"hs": "haskell", "lhs": "haskell",
|
||||
"ex": "elixir", "exs": "elixir", "eex": "elixir",
|
||||
"heex": "elixir", "leex": "elixir",
|
||||
"erl": "erlang", "hrl": "erlang", "escript": "erlang",
|
||||
"clj": "clojure", "cljs": "clojure", "cljc": "clojure", "edn": "clojure",
|
||||
"ml": "ocaml", "mli": "ocaml",
|
||||
"fs": "fsharp", "fsi": "fsharp", "fsx": "fsharp", "fsscript": "fsharp",
|
||||
"nim": "nim", "nims": "nim",
|
||||
"d": "d", "di": "d",
|
||||
"m": "objectivec", "mm": "objectivec",
|
||||
"vb": "vbnet", "vbs": "vbnet", "bas": "vbnet",
|
||||
"pas": "delphi", "pp": "delphi", "dpr": "delphi",
|
||||
"vhd": "vhdl", "vhdl": "vhdl",
|
||||
"v": "verilog", "vh": "verilog", "sv": "verilog", "svh": "verilog",
|
||||
"asm": "x86asm", "s": "x86asm", "nasm": "x86asm", "inc": "x86asm",
|
||||
"ada": "ada", "ads": "ada", "adb": "ada",
|
||||
"f": "fortran", "for": "fortran", "f90": "fortran", "f95": "fortran",
|
||||
"f03": "fortran", "f08": "fortran",
|
||||
"cob": "plaintext", "cbl": "plaintext", "cpy": "plaintext",
|
||||
"dockerfile": "dockerfile",
|
||||
"tf": "plaintext", "tfvars": "plaintext", "hcl": "plaintext",
|
||||
"cmake": "cmake",
|
||||
"makefile": "makefile", "mk": "makefile", "mak": "makefile",
|
||||
"gql": "graphql", "graphql": "graphql", "graphqls": "graphql",
|
||||
"proto": "protobuf", "thrift": "thrift",
|
||||
"vim": "vim", "vimrc": "vim", "gvimrc": "vim",
|
||||
"awk": "awk",
|
||||
"gcode": "gcode", "gco": "gcode", "nc": "gcode", "ngc": "gcode",
|
||||
"http": "http", "rest": "http",
|
||||
"nginx": "nginx", "apache": "apache", "htaccess": "apache",
|
||||
"wat": "wasm",
|
||||
"diff": "diff", "patch": "diff", "rej": "diff",
|
||||
"bib": "plaintext", "bbl": "plaintext",
|
||||
"txt": "plaintext", "log": "plaintext", "text": "plaintext",
|
||||
"csv": "plaintext", "tsv": "plaintext", "tab": "plaintext",
|
||||
"nfo": "plaintext", "asc": "plaintext",
|
||||
"lock": "plaintext",
|
||||
"gitignore": "plaintext", "gitattributes": "plaintext",
|
||||
"gitkeep": "plaintext", "dockerignore": "plaintext",
|
||||
"npmignore": "plaintext",
|
||||
}
|
||||
# Aliases users may type in markdown fences / old block labels.
|
||||
_CODE_ALIASES = {
|
||||
"py": "python", "js": "javascript", "jsx": "javascript",
|
||||
"ts": "typescript", "tsx": "typescript",
|
||||
"html": "xml", "htm": "xml", "markup": "xml",
|
||||
"yml": "yaml", "md": "markdown",
|
||||
"sh": "bash", "zsh": "bash", "console": "shell",
|
||||
"ps1": "powershell", "ps": "powershell",
|
||||
"bat": "dos", "cmd": "dos", "batch": "dos",
|
||||
"c++": "cpp", "c#": "csharp", "cs": "csharp",
|
||||
"kt": "kotlin", "rb": "ruby", "rs": "rust", "pl": "perl",
|
||||
"toml": "ini", "cfg": "ini", "conf": "ini", "env": "ini",
|
||||
"tex": "latex", "docker": "dockerfile", "make": "makefile",
|
||||
"plain text": "plaintext", "plain": "plaintext", "text": "plaintext",
|
||||
"none": "plaintext", "": "plaintext",
|
||||
}
|
||||
# highlight.js ids served by static/js/highlight.min.js + highlight-extra.min.js.
|
||||
_HLJS_LANGS = frozenset({
|
||||
"ada", "apache", "armasm", "awk", "bash", "c", "clojure", "cmake", "cpp",
|
||||
"csharp", "css", "d", "dart", "delphi", "diff", "dockerfile", "dos",
|
||||
"elixir", "erlang", "fsharp", "fortran", "gcode", "go",
|
||||
"graphql", "groovy", "haskell", "http", "ini", "java", "javascript",
|
||||
"json", "julia", "kotlin", "latex", "less", "lua", "makefile",
|
||||
"markdown", "matlab", "nginx", "nim", "objectivec", "ocaml", "perl",
|
||||
"php", "php-template", "plaintext", "powershell", "protobuf", "python",
|
||||
"python-repl", "r", "ruby", "rust", "scala", "scss", "shell", "sql",
|
||||
"stan", "stata", "swift", "thrift", "typescript", "vbnet", "verilog",
|
||||
"vhdl", "vim", "wasm", "x86asm", "xml", "yaml",
|
||||
})
|
||||
|
||||
|
||||
def normalize_code_lang(lang: str | None) -> str:
|
||||
"""Normalize any code language label to a bundled highlight.js id.
|
||||
|
||||
Accepts highlight.js ids, file extensions, markdown fence names and the
|
||||
legacy ``Plain Text`` label. Unknown values degrade to ``plaintext``.
|
||||
"""
|
||||
lang = (lang or "").strip().lower()
|
||||
if not lang or lang in ("plain text", "none"):
|
||||
return "plaintext"
|
||||
if lang in _CODE_ALIASES:
|
||||
lang = _CODE_ALIASES[lang]
|
||||
if lang in _CODE_LANG.values() or lang in _HLJS_LANGS:
|
||||
return lang
|
||||
return "plaintext"
|
||||
|
||||
|
||||
def code_lang_for_file(filename: str | None) -> str:
|
||||
"""Best highlight.js id for an uploaded / repo file name."""
|
||||
name = (filename or "").replace("\\", "/").rsplit("/", 1)[-1].lower()
|
||||
if not name:
|
||||
return "plaintext"
|
||||
if name in _FILENAME_LANG:
|
||||
return _FILENAME_LANG[name]
|
||||
ext = name.rsplit(".", 1)[-1] if "." in name else ""
|
||||
return _CODE_LANG.get(ext, "plaintext")
|
||||
|
||||
|
||||
_MARKDOWN_MIMES = {"text/markdown", "text/x-markdown", "application/octet-stream"}
|
||||
|
||||
|
||||
@@ -119,7 +317,8 @@ def _file_text(page: dict) -> str | None:
|
||||
name = (rel.rsplit("/", 1)[-1] or "").lower()
|
||||
ext = name.rsplit(".", 1)[-1] if "." in name else ""
|
||||
mime = (meta.get("mime_type") or "").lower()
|
||||
if not (ext in _TEXTUAL_EXTS or mime.startswith("text/")):
|
||||
if not (ext in _TEXTUAL_EXTS or name in _TEXTUAL_FILENAMES
|
||||
or mime.startswith("text/")):
|
||||
return None
|
||||
try:
|
||||
full = (_data_root() / rel).resolve()
|
||||
@@ -161,8 +360,7 @@ def _page_source(page: dict):
|
||||
mime = (meta.get("mime_type") or "").lower()
|
||||
if ext in ("md", "markdown") or mime in _MARKDOWN_MIMES or mime.startswith("text/markdown"):
|
||||
return "md", text
|
||||
lang = _CODE_LANG.get(ext, "plaintext")
|
||||
return "code", (text, lang)
|
||||
return "code", (text, code_lang_for_file(name))
|
||||
|
||||
# Unknown format (e.g. legacy) -> try to dump as raw text
|
||||
if content.strip():
|
||||
@@ -497,7 +695,7 @@ def blocks_to_markdown(blocks: list) -> str:
|
||||
out.append("---")
|
||||
elif t == "code":
|
||||
lang = b.get("language") or ""
|
||||
out.append(f"```{lang}\n{c}\n```")
|
||||
out.append(f"```{normalize_code_lang(lang) if lang else ''}\n{c}\n```")
|
||||
elif t == "toggle":
|
||||
out.append(f"### {c}")
|
||||
if b.get("children"):
|
||||
@@ -615,8 +813,11 @@ def blocks_to_html(blocks: list) -> str:
|
||||
parts.append("<hr>")
|
||||
elif t == "code":
|
||||
lang = b.get("language") or ""
|
||||
hljs_lang = normalize_code_lang(lang)
|
||||
label = f'<div class="code-lang">{_text(lang)}</div>' if lang else ""
|
||||
parts.append(f"<pre>{label}<code>{c}</code></pre>")
|
||||
parts.append(
|
||||
f'<pre>{label}<code class="language-{hljs_lang}">{c}</code></pre>'
|
||||
)
|
||||
elif t == "math":
|
||||
parts.append(f'<div class="math">\\[{c}\\]</div>')
|
||||
elif t == "table_of_contents":
|
||||
|
||||
Reference in New Issue
Block a user