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.
349 lines
15 KiB
JavaScript
349 lines
15 KiB
JavaScript
/* ═══════════════════════════════════════════════════════════════════
|
|
FlowDeck — central syntax-highlighting helper (highlight.js, vendored).
|
|
|
|
Single source of truth mapping file extensions / well-known filenames to
|
|
highlight.js language ids. Covers ~230 file types (code, config, IaC,
|
|
docs). Every code viewer (Gitea file view, page-editor code blocks & file
|
|
viewer, workspace previews, public pages) must go through this helper so a
|
|
new language is added in ONE place.
|
|
|
|
Requires (load order): highlight.min.js, highlight-extra.min.js.
|
|
All functions are defensive: without hljs they degrade to escaped plain
|
|
text — never throw, never use eval (CSP-safe).
|
|
═══════════════════════════════════════════════════════════════════ */
|
|
(function () {
|
|
'use strict';
|
|
|
|
/* Extension (lowercase, no dot) → highlight.js language id.
|
|
Ids must exist in static/js/highlight.min.js (common bundle) or
|
|
static/js/highlight-extra.min.js — anything else falls back to the
|
|
alias table / plaintext via normalizeLang(). */
|
|
var EXT_TO_LANG = {
|
|
// ── Python ──
|
|
py: 'python', pyw: 'python', pyi: 'python', gyp: 'python', gypi: 'python',
|
|
// ── JavaScript / TypeScript ──
|
|
js: 'javascript', mjs: 'javascript', cjs: 'javascript', jsx: 'javascript',
|
|
ts: 'typescript', mts: 'typescript', cts: 'typescript', tsx: 'typescript',
|
|
// ── Web markup / styles ──
|
|
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', vue: 'xml', svelte: 'xml',
|
|
astro: 'xml', axml: 'xml',
|
|
css: 'css', scss: 'scss', sass: 'scss', less: 'less',
|
|
// ── Data / config ──
|
|
json: 'json', json5: 'json', jsonc: 'json', geojson: 'json',
|
|
webmanifest: 'json', avsc: 'json', har: 'json',
|
|
yaml: 'yaml', yml: 'yaml',
|
|
toml: 'ini', ini: 'ini', cfg: 'ini', conf: 'ini', env: 'ini',
|
|
properties: 'ini', prop: 'ini', gitconfig: 'ini', gitmodules: 'ini',
|
|
editorconfig: 'ini', inf: 'ini', url: 'ini', reg: 'ini',
|
|
// ── Markdown / docs ──
|
|
md: 'markdown', markdown: 'markdown', mkd: 'markdown', mdwn: 'markdown',
|
|
mdown: 'markdown', rmd: 'markdown', qmd: 'markdown',
|
|
tex: 'latex', latex: 'latex', sty: 'latex', cls: 'latex', dtx: 'latex',
|
|
// ── SQL ──
|
|
sql: 'sql', pgsql: 'sql', psql: 'sql', mysql: 'sql', mssql: 'sql',
|
|
// ── Shells ──
|
|
// NOTE: no `.shell` extension key (unused); the `shell` grammar stays
|
|
// available via `fish`, aliases and the language picker.
|
|
sh: 'bash', bash: 'bash', zsh: 'bash', ksh: 'bash', mksh: 'bash',
|
|
fish: 'shell', yash: 'bash',
|
|
ps1: 'powershell', psm1: 'powershell', psd1: 'powershell',
|
|
bat: 'dos', cmd: 'dos',
|
|
// ── Systems ──
|
|
c: 'c', h: 'c',
|
|
cpp: 'cpp', cc: 'cpp', cxx: 'cpp', 'c++': 'cpp', hh: 'cpp',
|
|
hpp: 'cpp', hxx: 'cpp', hcc: 'cpp', ino: 'cpp', pde: 'cpp',
|
|
cs: 'csharp', csx: 'csharp', cake: 'csharp',
|
|
rs: 'rust',
|
|
go: 'go',
|
|
swift: 'swift',
|
|
kt: 'kotlin', kts: 'kotlin',
|
|
java: 'java', jsp: 'java',
|
|
scala: 'scala', sc: 'scala',
|
|
groovy: 'groovy', gvy: 'groovy', gradle: 'groovy',
|
|
dart: 'dart',
|
|
lua: 'lua',
|
|
// NOTE: no `.perl` / `.t` / `.mat` / `.app` / `.gpg` / `.wasm` keys on
|
|
// purpose (unused, ambiguous or binary extensions).
|
|
pl: 'perl', pm: 'perl', pod: 'perl',
|
|
php: 'php', phtml: 'php-template',
|
|
r: 'r', jl: 'julia',
|
|
rb: 'ruby', gemspec: 'ruby', rake: 'ruby', rbw: 'ruby',
|
|
// ── Functional / misc ──
|
|
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',
|
|
// ── HDL / asm / legacy ──
|
|
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',
|
|
// ── Build / IaC / containers ──
|
|
dockerfile: 'dockerfile',
|
|
tf: 'plaintext', tfvars: 'plaintext', hcl: 'plaintext',
|
|
cmake: 'cmake',
|
|
makefile: 'makefile', mk: 'makefile', mak: 'makefile',
|
|
ninja: 'ini',
|
|
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',
|
|
// ── Web servers / protocols ──
|
|
http: 'http', rest: 'http',
|
|
nginx: 'nginx',
|
|
apache: 'apache', htaccess: 'apache',
|
|
wat: 'wasm',
|
|
// ── Diffs / misc text ──
|
|
diff: 'diff', patch: 'diff', rej: 'diff',
|
|
log: 'plaintext', txt: 'plaintext', text: 'plaintext',
|
|
csv: 'plaintext', tsv: 'plaintext', tab: 'plaintext',
|
|
lock: 'plaintext', gitignore: 'plaintext', gitattributes: 'plaintext',
|
|
gitkeep: 'plaintext', dockerignore: 'plaintext', npmignore: 'plaintext',
|
|
bib: 'plaintext', bbl: 'plaintext', nfo: 'plaintext', asc: 'plaintext',
|
|
};
|
|
|
|
/* Exact basenames (lowercase) → language, checked BEFORE the extension
|
|
(Dockerfile / Makefile / go.mod … often carry no usable extension). */
|
|
var FILE_TO_LANG = {
|
|
'dockerfile': 'dockerfile', 'containerfile': 'dockerfile',
|
|
'docker-compose.yml': 'yaml', 'docker-compose.yaml': 'yaml',
|
|
'compose.yml': 'yaml', 'compose.yaml': 'yaml',
|
|
'makefile': 'makefile', 'gnumakefile': 'makefile',
|
|
'cmakelists.txt': 'cmake',
|
|
'go.mod': 'go', 'go.sum': 'plaintext', 'go.work': 'go',
|
|
'cargo.toml': 'ini', 'cargo.lock': 'plaintext',
|
|
'package.json': 'json', 'package-lock.json': 'json',
|
|
'composer.json': 'json', 'tsconfig.json': 'json',
|
|
'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',
|
|
'rakefile': 'ruby', 'gemfile': 'ruby', 'vagrantfile': 'ruby',
|
|
'podfile': 'ruby', 'fastfile': 'ruby', 'dangerfile': 'ruby',
|
|
'brewfile': 'ruby', 'berksfile': 'ruby',
|
|
'build.gradle': 'groovy', 'settings.gradle': 'groovy',
|
|
'build.gradle.kts': 'kotlin', 'settings.gradle.kts': 'kotlin',
|
|
'.gitconfig': 'ini', '.gitmodules': 'ini', '.editorconfig': 'ini',
|
|
'.htaccess': 'apache', '.viminfo': 'vim',
|
|
};
|
|
|
|
/* Aliases users may type (markdown fences, old block labels, Prism ids…)
|
|
→ canonical highlight.js id. */
|
|
var ALIAS_TO_LANG = {
|
|
'py': 'python', 'python3': 'python', 'gyp': 'python',
|
|
'js': 'javascript', 'jsx': 'javascript', 'mjs': 'javascript',
|
|
'cjs': 'javascript', 'node': 'javascript',
|
|
'ts': 'typescript', 'tsx': 'typescript',
|
|
'html': 'xml', 'htm': 'xml', 'xhtml': 'xml', 'svg': 'xml',
|
|
'markup': 'xml', 'vue': 'xml', 'svelte': 'xml',
|
|
'yml': 'yaml', 'yaml': 'yaml',
|
|
'md': 'markdown', 'mkd': 'markdown',
|
|
'sh': 'bash', 'shell': 'shell', 'zsh': 'bash', 'console': 'shell',
|
|
'ps1': 'powershell', 'ps': 'powershell', 'powershell': 'powershell',
|
|
'bat': 'dos', 'cmd': 'dos', 'batch': 'dos',
|
|
'shell-session': 'shell', 'shellsession': 'shell',
|
|
'c++': 'cpp', 'cc': 'cpp', 'hpp': 'cpp', 'h': 'c',
|
|
'c#': 'csharp', 'csharp': 'csharp', 'cs': 'csharp',
|
|
'kt': 'kotlin', 'kts': 'kotlin',
|
|
'rb': 'ruby',
|
|
'rs': 'rust',
|
|
'golang': 'go',
|
|
'pl': 'perl', 'pm': 'perl',
|
|
'm': 'objectivec', 'mm': 'objectivec', 'objc': 'objectivec',
|
|
'r': 'r',
|
|
'jl': 'julia',
|
|
'toml': 'ini', 'ini': 'ini', 'cfg': 'ini', 'conf': 'ini',
|
|
'env': 'ini', 'properties': 'ini',
|
|
'tex': 'latex', 'latex': 'latex',
|
|
'docker': 'dockerfile',
|
|
'make': 'makefile',
|
|
'patch': 'diff',
|
|
'text': 'plaintext', 'plain': 'plaintext', 'plain text': 'plaintext',
|
|
'plaintext': 'plaintext', 'txt': 'plaintext', 'log': 'plaintext',
|
|
'none': 'plaintext', '': 'plaintext',
|
|
};
|
|
|
|
/* Language id → short human label for the code-block badge / selector. */
|
|
var LANG_LABELS = {
|
|
python: 'Python', javascript: 'JavaScript', typescript: 'TypeScript',
|
|
xml: 'HTML/XML', css: 'CSS', scss: 'SCSS', less: 'Less',
|
|
json: 'JSON', yaml: 'YAML', ini: 'INI/Config', markdown: 'Markdown',
|
|
latex: 'LaTeX', sql: 'SQL', bash: 'Bash', shell: 'Shell',
|
|
powershell: 'PowerShell', dos: 'Batch', c: 'C', cpp: 'C++',
|
|
csharp: 'C#', rust: 'Rust', go: 'Go', swift: 'Swift', kotlin: 'Kotlin',
|
|
java: 'Java', scala: 'Scala', groovy: 'Groovy', dart: 'Dart',
|
|
lua: 'Lua', perl: 'Perl', php: 'PHP', 'php-template': 'PHP',
|
|
r: 'R', julia: 'Julia', ruby: 'Ruby', haskell: 'Haskell',
|
|
elixir: 'Elixir', erlang: 'Erlang', clojure: 'Clojure',
|
|
ocaml: 'OCaml', fsharp: 'F#', nim: 'Nim', d: 'D',
|
|
objectivec: 'Objective-C', matlab: 'MATLAB', vbnet: 'VB.NET',
|
|
delphi: 'Delphi', vhdl: 'VHDL', verilog: 'Verilog',
|
|
x86asm: 'ASM', armasm: 'ARM ASM', ada: 'Ada', fortran: 'Fortran',
|
|
dockerfile: 'Docker', cmake: 'CMake', makefile: 'Makefile',
|
|
graphql: 'GraphQL', protobuf: 'Protobuf', thrift: 'Thrift',
|
|
vim: 'Vim', awk: 'AWK', gcode: 'G-code',
|
|
http: 'HTTP', nginx: 'Nginx', apache: 'Apache', wasm: 'WASM',
|
|
diff: 'Diff', plaintext: 'Plain Text',
|
|
};
|
|
|
|
/** Languages offered in the code-block language picker (id order). */
|
|
var PICKER_LANGS = [
|
|
'plaintext', 'python', 'javascript', 'typescript', 'xml', 'css', 'scss',
|
|
'json', 'yaml', 'ini', 'markdown', 'sql', 'bash', 'shell', 'powershell',
|
|
'dos', 'c', 'cpp', 'csharp', 'rust', 'go', 'swift', 'kotlin', 'java',
|
|
'scala', 'groovy', 'dart', 'lua', 'perl', 'php', 'r', 'julia', 'ruby',
|
|
'haskell', 'elixir', 'erlang', 'clojure', 'ocaml', 'fsharp', 'nim', 'd',
|
|
'objectivec', 'dockerfile', 'cmake', 'makefile', 'graphql', 'protobuf',
|
|
'latex', 'vim', 'http', 'nginx', 'diff',
|
|
];
|
|
|
|
function hasHljs() {
|
|
return typeof window.hljs !== 'undefined' && !!window.hljs.getLanguage;
|
|
}
|
|
|
|
function escHtml(s) {
|
|
return String(s == null ? '' : s)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>');
|
|
}
|
|
|
|
/** Normalize any user label / extension / Prism id to a bundled hljs id. */
|
|
function normalizeLang(lang) {
|
|
var l = String(lang == null ? '' : lang).trim().toLowerCase();
|
|
if (!l || l === 'plain text' || l === 'none') return 'plaintext';
|
|
if (ALIAS_TO_LANG[l]) return ALIAS_TO_LANG[l];
|
|
if (!hasHljs()) return l || 'plaintext';
|
|
try {
|
|
if (window.hljs.getLanguage(l)) return l;
|
|
} catch (e) { /* ignore → plaintext */ }
|
|
return 'plaintext';
|
|
}
|
|
|
|
/** Best highlight.js id for a file path (basename map, then extension). */
|
|
function langForFile(path) {
|
|
var name = String(path || '').replace(/\\/g, '/').split('/').pop().toLowerCase();
|
|
if (!name) return 'plaintext';
|
|
if (FILE_TO_LANG[name]) return FILE_TO_LANG[name];
|
|
// dotfiles like ".bashrc" without another dot are covered above; a
|
|
// trailing ".xyz" is the extension otherwise.
|
|
var dot = name.lastIndexOf('.');
|
|
if (dot < 0) return 'plaintext';
|
|
var ext = name.slice(dot + 1);
|
|
if (FILE_TO_LANG['.' + name]) return FILE_TO_LANG['.' + name];
|
|
if (EXT_TO_LANG[ext]) return EXT_TO_LANG[ext];
|
|
return 'plaintext';
|
|
}
|
|
|
|
function labelFor(lang) {
|
|
var n = normalizeLang(lang);
|
|
return LANG_LABELS[n] || n;
|
|
}
|
|
|
|
/** Highlight `text` → HTML string (escaped when hljs/lang missing). */
|
|
function highlightText(text, lang) {
|
|
var src = String(text == null ? '' : text);
|
|
var n = normalizeLang(lang);
|
|
if (n === 'plaintext' || !hasHljs()) return escHtml(src);
|
|
try {
|
|
return window.hljs.highlight(src, { language: n }).value;
|
|
} catch (e) {
|
|
return escHtml(src);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Highlight a <code> element in place. The element's TEXT is preserved
|
|
* (highlight.js reads textContent), so this is safe for contenteditable
|
|
* code blocks: sync() re-reads text, spans are transparent.
|
|
* Re-highlighting is skipped when neither language nor text changed
|
|
* (cheap length+prefix fingerprint) — e.g. repeated editor renders.
|
|
*/
|
|
function highlightBlock(el, lang) {
|
|
if (!el) return;
|
|
var n = normalizeLang(lang || el.getAttribute('data-hljs-lang') || langClassOf(el));
|
|
el.setAttribute('data-hljs-lang', n);
|
|
if (n !== 'plaintext') {
|
|
el.classList.add('hljs');
|
|
el.classList.add('language-' + n);
|
|
}
|
|
if (!hasHljs() || n === 'plaintext') return;
|
|
try {
|
|
var fp = fingerprint(el);
|
|
if (el.dataset && el.dataset.hljsFp === fp) return; // unchanged
|
|
window.hljs.highlightElement(el);
|
|
if (el.dataset) el.dataset.hljsFp = fp;
|
|
} catch (e) { /* keep plain text */ }
|
|
}
|
|
|
|
function langClassOf(el) {
|
|
if (!el || !el.className) return '';
|
|
var m = /(?:^|\s)language-([\w-]+)/.exec(el.className);
|
|
return m ? m[1] : '';
|
|
}
|
|
|
|
function fingerprint(el) {
|
|
var t = el.textContent || '';
|
|
return (el.getAttribute('data-hljs-lang') || '') + '|' + t.length + ':' + t.slice(0, 64);
|
|
}
|
|
|
|
/**
|
|
* Highlight every `pre code` viewer block under `root`:
|
|
* `[data-hljs]` (explicit, e.g. editor code blocks) and
|
|
* `code[class*=language-]` (Gitea viewer, exports, public pages).
|
|
* Editable code blocks are included — their text round-trips losslessly.
|
|
*/
|
|
function highlightAllIn(root) {
|
|
var scope = root || document;
|
|
if (!hasHljs() || !scope.querySelectorAll) return 0;
|
|
var count = 0;
|
|
try {
|
|
var nodes = scope.querySelectorAll(
|
|
'pre code[data-hljs], pre code[data-hljs-lang], pre code[class*="language-"]'
|
|
);
|
|
for (var i = 0; i < nodes.length; i++) {
|
|
var el = nodes[i];
|
|
var lang = el.getAttribute('data-hljs-lang') || langClassOf(el) || 'plaintext';
|
|
var n = normalizeLang(lang);
|
|
if (n === 'plaintext') continue;
|
|
el.setAttribute('data-hljs-lang', n);
|
|
el.classList.add('hljs');
|
|
var fp = n + '|' + (el.textContent || '').length + ':' + (el.textContent || '').slice(0, 64);
|
|
if (el.dataset && el.dataset.hljsFp === fp) continue;
|
|
window.hljs.highlightElement(el);
|
|
if (el.dataset) el.dataset.hljsFp = fp;
|
|
count++;
|
|
}
|
|
} catch (e) { /* never break rendering */ }
|
|
return count;
|
|
}
|
|
|
|
window.FDHighlight = {
|
|
EXT_TO_LANG: EXT_TO_LANG,
|
|
FILE_TO_LANG: FILE_TO_LANG,
|
|
PICKER_LANGS: PICKER_LANGS,
|
|
normalizeLang: normalizeLang,
|
|
langForFile: langForFile,
|
|
labelFor: labelFor,
|
|
highlightText: highlightText,
|
|
highlightBlock: highlightBlock,
|
|
highlightAllIn: highlightAllIn,
|
|
hasHljs: hasHljs,
|
|
};
|
|
})();
|