REGRESSION FROM b22bb11b (Wysiwyg integration).
While inserting the new Wysiwyg methods (wysiwygConfig getter, onWysiwygLoad,
onToggleSource) between setup() and the existing onMounted / onWillUnmount
hook calls, I accidentally closed setup() early with the new
`this.wysiwygEditors = {};` assignment. That left the original
`onMounted(async () => {...});` and `onWillUnmount(...);` calls dangling
INSIDE the class body but OUTSIDE any method - which is invalid JS.
JavaScript's class-body parser sees the bare `onMounted(async () => ...)`
and tries to interpret it as a method declaration where `onMounted` is the
name and the parens are the parameter list. `async () => {...}` is not a
valid parameter, so the bundle fails with:
Uncaught SyntaxError: Unexpected token '('
web.assets_web.min.js:28807
That single parse failure tanks the entire backend asset bundle, leaving
users with a completely blank screen on /odoo (and any other backend
route). Frontend bundle was unaffected.
FIX
Move the onMounted / onWillUnmount calls back inside setup() where they
belong. Add a load-bearing comment explaining why they must stay there so
this regression cannot silently come back during a future refactor.
VERIFIED
- line 51: setup() opens
- lines 87, 93: onMounted, onWillUnmount calls INSIDE setup
- line 142: _initDrawflow as a normal class method (outside setup)
- upgrade clean
- bundle 10029245 bytes, exactly one onMounted( occurrence in
FlowchartDesigner class body
- node --check on the freshly-rendered web.assets_web.min.js -> PARSE_OK
Bumped to 19.0.2.2.3.
Co-authored-by: Cursor <cursoragent@cursor.com>
153 lines
9.5 KiB
XML
153 lines
9.5 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
|
<templates xml:space="preserve">
|
|
|
|
<t t-name="fusion_repairs.FlowchartDesigner">
|
|
<div class="fr-designer-wrap">
|
|
<div class="fr-designer-toolbar">
|
|
<span class="fr-designer-title" t-if="state.chart">
|
|
<strong t-out="state.chart.name"/>
|
|
<span class="text-muted ms-2">v<t t-out="state.chart.version"/></span>
|
|
</span>
|
|
<div class="fr-designer-toolbar-actions">
|
|
<div class="btn-group btn-group-sm me-2">
|
|
<button class="btn btn-outline-primary" t-on-click="() => this.onAddNode('question')">
|
|
<i class="fa fa-plus"/> Question
|
|
</button>
|
|
<button class="btn btn-outline-success" t-on-click="() => this.onAddNode('suggestion')">
|
|
<i class="fa fa-plus"/> Suggestion
|
|
</button>
|
|
<button class="btn btn-outline-secondary" t-on-click="() => this.onAddNode('info')">
|
|
<i class="fa fa-plus"/> Info
|
|
</button>
|
|
<button class="btn btn-outline-danger" t-on-click="() => this.onAddNode('outcome')">
|
|
<i class="fa fa-plus"/> Outcome
|
|
</button>
|
|
</div>
|
|
<div class="btn-group btn-group-sm me-2">
|
|
<button class="btn btn-light" t-on-click="onZoomOut"><i class="fa fa-search-minus"/></button>
|
|
<button class="btn btn-light" t-on-click="onZoomReset">100%</button>
|
|
<button class="btn btn-light" t-on-click="onZoomIn"><i class="fa fa-search-plus"/></button>
|
|
</div>
|
|
<button class="btn btn-primary btn-sm"
|
|
t-on-click="onSave"
|
|
t-att-disabled="state.saving">
|
|
<i class="fa fa-save"/>
|
|
<t t-if="state.saving">Saving...</t>
|
|
<t t-elif="state.dirty">Save *</t>
|
|
<t t-else="">Save</t>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="fr-designer-body">
|
|
<div class="fr-designer-canvas-wrap">
|
|
<div t-ref="canvas" class="drawflow"/>
|
|
</div>
|
|
|
|
<div t-ref="editorPanel" class="fr-designer-editor">
|
|
<h6>Node Editor</h6>
|
|
<t t-if="!selectedMeta">
|
|
<p class="text-muted small">Click a node on the canvas to edit it. Drag from a node's right edge to another node to create an edge.</p>
|
|
</t>
|
|
<t t-if="selectedMeta">
|
|
<div class="mb-2">
|
|
<label class="form-label small mb-1">Title</label>
|
|
<input class="form-control form-control-sm"
|
|
t-att-value="selectedMeta.name"
|
|
t-on-change="(ev) => this.onEditorFieldChange('name', ev.target.value)"/>
|
|
</div>
|
|
<div class="mb-2">
|
|
<label class="form-label small mb-1">Type</label>
|
|
<select class="form-select form-select-sm"
|
|
t-on-change="(ev) => this.onEditorFieldChange('node_type', ev.target.value)">
|
|
<option value="question" t-att-selected="selectedMeta.node_type === 'question'">Question</option>
|
|
<option value="suggestion" t-att-selected="selectedMeta.node_type === 'suggestion'">Suggestion</option>
|
|
<option value="info" t-att-selected="selectedMeta.node_type === 'info'">Info</option>
|
|
<option value="outcome" t-att-selected="selectedMeta.node_type === 'outcome'">Outcome</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-2" t-if="selectedMeta.node_type === 'outcome'">
|
|
<label class="form-label small mb-1">Outcome Kind</label>
|
|
<select class="form-select form-select-sm"
|
|
t-on-change="(ev) => this.onEditorFieldChange('outcome_kind', ev.target.value)">
|
|
<option value="resolved" t-att-selected="selectedMeta.outcome_kind === 'resolved'">Resolved on call</option>
|
|
<option value="escalate" t-att-selected="selectedMeta.outcome_kind === 'escalate'">Escalate to tech</option>
|
|
<option value="order_part" t-att-selected="selectedMeta.outcome_kind === 'order_part'">Order part</option>
|
|
</select>
|
|
</div>
|
|
<div class="mb-2 fr-content-editor">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<label class="form-label small mb-0">Content (shown to CS)</label>
|
|
<button type="button"
|
|
class="btn btn-link btn-sm p-0"
|
|
t-on-click="onToggleSource">
|
|
<i t-att-class="state.sourceMode ? 'fa fa-eye' : 'fa fa-code'"/>
|
|
<t t-if="state.sourceMode"> Rich Text</t>
|
|
<t t-else=""> HTML Source</t>
|
|
</button>
|
|
</div>
|
|
<!-- Rich text (default) - re-mounts when selecting a different node via t-key. -->
|
|
<t t-if="!state.sourceMode">
|
|
<div class="fr-wysiwyg-shell">
|
|
<Wysiwyg t-key="'wysiwyg-' + state.selectedNodeId"
|
|
config="wysiwygConfig"
|
|
onLoad.bind="onWysiwygLoad"
|
|
contentClass="'fr-wysiwyg-content'"/>
|
|
</div>
|
|
<div class="form-text">Bold, italic, lists, links and inline images supported. Click <em>HTML Source</em> to paste raw markup.</div>
|
|
</t>
|
|
<!-- HTML source mode (toggle) - power user escape hatch. -->
|
|
<t t-else="">
|
|
<textarea class="form-control form-control-sm font-monospace"
|
|
rows="10"
|
|
t-on-change="(ev) => this.onEditorFieldChange('content_html', ev.target.value)"
|
|
t-out="selectedMeta.content_html"/>
|
|
<div class="form-text">Raw HTML mode - what you type is rendered as-is.</div>
|
|
</t>
|
|
</div>
|
|
<div class="mb-2">
|
|
<label class="form-label small mb-1">
|
|
Media (<t t-out="selectedMeta.media.length"/>)
|
|
</label>
|
|
<input type="file" class="form-control form-control-sm"
|
|
multiple="multiple"
|
|
accept="image/*,video/*"
|
|
t-on-change="onUploadMedia"/>
|
|
<div t-if="selectedMeta.media.length" class="fr-media-thumbs mt-2">
|
|
<t t-foreach="selectedMeta.media" t-as="m" t-key="m.id">
|
|
<a t-att-href="m.url" target="_blank" class="me-1">
|
|
<img t-att-src="m.url" class="fr-media-thumb" t-att-alt="m.name"/>
|
|
</a>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
<div class="mb-2">
|
|
<button class="btn btn-sm btn-outline-warning"
|
|
t-on-click="onSetStart"
|
|
t-att-disabled="selectedMeta.is_start">
|
|
<i class="fa fa-flag"/>
|
|
<t t-if="selectedMeta.is_start">Start Node</t>
|
|
<t t-else="">Make Start Node</t>
|
|
</button>
|
|
</div>
|
|
<hr/>
|
|
<div t-if="outgoingEdgesForSelected.length">
|
|
<h6 class="small">Outgoing Edges</h6>
|
|
<t t-foreach="outgoingEdgesForSelected" t-as="e" t-key="e.key">
|
|
<div class="mb-1">
|
|
<label class="form-label small mb-0">to <em t-out="e.target_name"/></label>
|
|
<input class="form-control form-control-sm"
|
|
placeholder="Label (Yes / No / ...)"
|
|
t-att-value="e.label"
|
|
t-on-change="(ev) => this.onEdgeLabelChange(ev, e.key)"/>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</t>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</t>
|
|
|
|
</templates>
|