|
2 | 2 | // License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl). |
3 | 3 |
|
4 | 4 | import {Component, onWillStart, useState} from "@odoo/owl"; |
| 5 | +import {CodeEditor} from "@web/core/code_editor/code_editor"; |
5 | 6 | import {previewRegistry} from "./preview_registry.esm"; |
6 | 7 |
|
7 | 8 | // --------------------------------------------------------------------------- |
@@ -62,6 +63,65 @@ export class TextPreview extends Component { |
62 | 63 | } |
63 | 64 | } |
64 | 65 |
|
| 66 | +// Code / source text: reuse Odoo's bundled CodeEditor (ACE) for a read-only, |
| 67 | +// syntax-highlighted preview — a step up from the raw-bytes iframe for |
| 68 | +// .py/.js/.scss/.css source files and a no-new-dependency win (the asset is |
| 69 | +// already in the backend bundle). Content is fetched once on mount; the file |
| 70 | +// extension picks the ACE mode. Odoo's bundled ACE ships only a handful of |
| 71 | +// modes (CodeEditor.MODES: python / javascript / xml / qweb / scss) — any |
| 72 | +// other mode 404s on its mode-*.js. These extensions are exactly the ones |
| 73 | +// _effectiveMimetype maps to the dedicated _CODE_MIMETYPES this handler claims; |
| 74 | +// browser-readable JSON/XML/HTML stay in TextPreview's iframe. |
| 75 | +const _ACE_MODES = { |
| 76 | + py: "python", |
| 77 | + js: "javascript", |
| 78 | + mjs: "javascript", |
| 79 | + cjs: "javascript", |
| 80 | + scss: "scss", |
| 81 | + css: "scss", |
| 82 | + sass: "scss", |
| 83 | + less: "scss", |
| 84 | +}; |
| 85 | + |
| 86 | +export class CodePreview extends Component { |
| 87 | + static template = "dms.preview.Code"; |
| 88 | + static components = {CodeEditor}; |
| 89 | + static props = fileProps; |
| 90 | + |
| 91 | + setup() { |
| 92 | + this.state = useState({content: "", error: null}); |
| 93 | + onWillStart(async () => { |
| 94 | + // Only the CodeEditor path needs the content string; the iframe |
| 95 | + // fallback streams it via src. |
| 96 | + if (!this.aceMode) { |
| 97 | + return; |
| 98 | + } |
| 99 | + try { |
| 100 | + const r = await fetch(this.src); |
| 101 | + if (!r.ok) { |
| 102 | + throw new Error(`HTTP ${r.status}`); |
| 103 | + } |
| 104 | + this.state.content = await r.text(); |
| 105 | + } catch (e) { |
| 106 | + this.state.error = String(e.message || e); |
| 107 | + } |
| 108 | + }); |
| 109 | + } |
| 110 | + |
| 111 | + get aceMode() { |
| 112 | + const ext = (this.props.file.name || "").split(".").pop().toLowerCase(); |
| 113 | + return _ACE_MODES[ext] || null; |
| 114 | + } |
| 115 | + |
| 116 | + get src() { |
| 117 | + const ts = encodeURIComponent(this.props.file.write_date || ""); |
| 118 | + return ( |
| 119 | + `/web/content?id=${this.props.file.id}&model=dms.file` + |
| 120 | + `&field=content&filename_field=name&v=${ts}` |
| 121 | + ); |
| 122 | + } |
| 123 | +} |
| 124 | + |
65 | 125 | // Markdown: client-side render to HTML, displayed in a sandboxed iframe |
66 | 126 | // via srcdoc. The sandbox attribute denies scripts and top navigation so |
67 | 127 | // untrusted markdown can't execute as XSS. The render covers the common |
@@ -227,6 +287,18 @@ reg.add("text/*", { |
227 | 287 | mt === "application/javascript", |
228 | 288 | score: 0, |
229 | 289 | }); |
| 290 | +// Syntax-highlighted code editor for genuine source files. Claims ONLY the |
| 291 | +// dedicated source-code mimetypes that _effectiveMimetype derives from a code |
| 292 | +// extension (.py/.js/.scss/...) — NOT the browser-readable text/plain, JSON or |
| 293 | +// XML, which the browser renders fine in TextPreview's iframe (and which the |
| 294 | +// dispatch contract keeps there). Wins over text/* (score 0) for those code |
| 295 | +// mimetypes; still below Markdown (score 5) so .md renders rich. |
| 296 | +const _CODE_MIMETYPES = new Set(["text/x-python", "text/javascript", "text/x-scss"]); |
| 297 | +reg.add("text/code", { |
| 298 | + component: CodePreview, |
| 299 | + match: (mt) => _CODE_MIMETYPES.has(mt), |
| 300 | + score: 2, |
| 301 | +}); |
230 | 302 | // Markdown rendering wins over the generic text/* handler (score 0) so |
231 | 303 | // `text/markdown` files render as formatted HTML instead of raw source. |
232 | 304 | reg.add("text/markdown", { |
|
0 commit comments