Replace the plain <textarea> in the flowchart designer's node-editor
right-panel with Odoo 19's native rich text editor so admins write
formatted prose / lists / bold / links / inline images without typing
HTML tags. The raw <textarea> stays available behind a toggle for the
power-user case (pasting markup from elsewhere, debugging).
CHANGES
manifest:
- depends += 'html_editor' (provides @html_editor/wysiwyg)
- bumped to 19.0.2.2.1
components/flowchart_designer/flowchart_designer.js:
- import { Wysiwyg } from '@html_editor/wysiwyg'
- import { MAIN_PLUGINS } from '@html_editor/plugin_sets'
- register Wysiwyg in static components
- state.sourceMode boolean (default false = rich text mode)
- wysiwygConfig getter builds the EditorConfig for the SELECTED node;
onChange reads editor.getContent() and writes back into the same
selectedMeta.content_html the rest of the designer already uses,
so the save path is unchanged
- onWysiwygLoad(editor) captures the editor instance per dfId so the
onChange callback can resolve the right one when nodes switch
- onToggleSource flushes the current editor's content before flipping
modes so unsaved keystrokes don't get lost
components/flowchart_designer/flowchart_designer.xml:
- replaced <textarea>...</textarea> with a conditional block:
sourceMode == false -> <Wysiwyg t-key="'wysiwyg-' + selectedNodeId"
config="wysiwygConfig"
onLoad="onWysiwygLoad.bind(this)"/>
sourceMode == true -> <textarea class="font-monospace" rows="10"/>
- t-key forces the editor to re-mount with the freshly-selected node's
content; otherwise switching nodes would keep showing the first
selected node's HTML
- new toolbar row above the editor has a "HTML Source" / "Rich Text"
toggle button (eye / code icons) so the user can flip at will
- hint text updated to reflect what each mode supports
components/flowchart_designer/flowchart_designer.scss:
- widened the right editor panel from 320px to 360px to give the
Wysiwyg toolbar room to breathe
- new .fr-wysiwyg-shell rule frames the embedded editor with the same
border + background as the other form-controls in the panel, with
a min-height of 180px and max-height 320px so it scrolls when the
content grows. Pins .o-we-toolbar inside the shell so it stays in
view as the user scrolls long content.
The save path, the runtime renderer, and the data model are unchanged -
content_html is still sanitised HTML stored on fusion.repair.flowchart.node.
Verified on local westin-v19:
- upgrade clean (no errors, no warnings)
- login serves 200 after restart
- 4 stale asset bundles flushed; Drawflow JS still served 46KB at
/fusion_repairs/static/src/lib/drawflow/drawflow.min.js
- Wysiwyg export confirmed at
/usr/lib/python3/dist-packages/odoo/addons/html_editor/static/src/wysiwyg.js:25
- MAIN_PLUGINS export confirmed at plugin_sets.js:103
Bumped to 19.0.2.2.1.
Co-authored-by: Cursor <cursoragent@cursor.com>
271 lines
5.8 KiB
SCSS
271 lines
5.8 KiB
SCSS
// Drawflow designer + runner shared theming.
|
|
// Uses the Bundle 1 SCSS token pattern with dark-mode-safe explicit hex.
|
|
|
|
$o-webclient-color-scheme: bright !default;
|
|
|
|
$_fr_page-hex: #f3f4f6;
|
|
$_fr_card-hex: #ffffff;
|
|
$_fr_border-hex: #d8dadd;
|
|
$_fr_text-muted-hex: #6b7280;
|
|
$_fr_panel-hex: #ffffff;
|
|
|
|
@if $o-webclient-color-scheme == dark {
|
|
$_fr_page-hex: #1a1d21 !global;
|
|
$_fr_card-hex: #22262d !global;
|
|
$_fr_border-hex: #2d3138 !global;
|
|
$_fr_text-muted-hex: #9aa1aa !global;
|
|
$_fr_panel-hex: #1f2329 !global;
|
|
}
|
|
|
|
$fr-page: var(--fr-page-bg, #{$_fr_page-hex});
|
|
$fr-card: var(--fr-card-bg, #{$_fr_card-hex});
|
|
$fr-border: var(--fr-border-color, #{$_fr_border-hex});
|
|
$fr-muted: var(--fr-text-muted, #{$_fr_text-muted-hex});
|
|
$fr-panel: var(--fr-panel-bg, #{$_fr_panel-hex});
|
|
|
|
.fr-designer-wrap {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: calc(100vh - 46px);
|
|
background: $fr-page;
|
|
}
|
|
|
|
.fr-designer-toolbar {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 8px 16px;
|
|
background: $fr-panel;
|
|
border-bottom: 1px solid $fr-border;
|
|
.fr-designer-title { font-size: 14px; }
|
|
.fr-designer-toolbar-actions { display: flex; gap: 4px; }
|
|
}
|
|
|
|
.fr-designer-body {
|
|
display: flex;
|
|
flex: 1;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.fr-designer-canvas-wrap {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
position: relative;
|
|
background: $fr-page;
|
|
.drawflow {
|
|
width: 100%;
|
|
height: 100%;
|
|
background: $fr-page !important;
|
|
}
|
|
}
|
|
|
|
.fr-designer-editor {
|
|
width: 360px;
|
|
border-left: 1px solid $fr-border;
|
|
background: $fr-panel;
|
|
padding: 12px 14px;
|
|
overflow-y: auto;
|
|
h6 { margin-bottom: 8px; font-weight: 700; }
|
|
}
|
|
|
|
// Frame the embedded Odoo Wysiwyg so it visually matches the other
|
|
// form-control fields in the right panel (border, padding, scrollable).
|
|
.fr-wysiwyg-shell {
|
|
border: 1px solid $fr-border;
|
|
border-radius: 4px;
|
|
background: $fr-card;
|
|
min-height: 180px;
|
|
max-height: 320px;
|
|
overflow-y: auto;
|
|
.odoo-editor-editable, .fr-wysiwyg-content {
|
|
min-height: 160px;
|
|
padding: 8px 10px;
|
|
outline: none;
|
|
}
|
|
.o-we-toolbar, .o_we_toolbar {
|
|
// Pin the Wysiwyg toolbar inside the shell so it scrolls with content.
|
|
z-index: 2;
|
|
}
|
|
}
|
|
|
|
// ----- Node card styling (inside Drawflow's drawflow_content_node) -----
|
|
.drawflow .drawflow-node {
|
|
background: transparent !important;
|
|
padding: 0 !important;
|
|
border: 0 !important;
|
|
box-shadow: none !important;
|
|
min-width: 220px;
|
|
&.selected .fr-node-card {
|
|
outline: 2px solid #2563eb;
|
|
outline-offset: 2px;
|
|
}
|
|
}
|
|
|
|
.fr-node-card {
|
|
background: $fr-card;
|
|
color: inherit;
|
|
border: 1px solid $fr-border;
|
|
border-top: 4px solid #6b7280;
|
|
border-radius: 6px;
|
|
width: 220px;
|
|
overflow: hidden;
|
|
font-size: 12px;
|
|
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
.fr-node-head {
|
|
padding: 4px 8px;
|
|
color: #ffffff;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
font-size: 10px;
|
|
letter-spacing: 0.05em;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.fr-start-badge,
|
|
.fr-outcome-badge {
|
|
background: rgba(0,0,0,0.25);
|
|
color: #ffffff;
|
|
border-radius: 3px;
|
|
font-size: 9px;
|
|
padding: 1px 5px;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.fr-node-title {
|
|
padding: 8px 10px 4px;
|
|
font-weight: 600;
|
|
color: #222;
|
|
}
|
|
|
|
.fr-node-body {
|
|
padding: 0 10px 6px;
|
|
color: $fr-muted;
|
|
max-height: 60px;
|
|
overflow: hidden;
|
|
font-size: 11px;
|
|
line-height: 1.4;
|
|
}
|
|
|
|
.fr-node-foot {
|
|
padding: 4px 10px 8px;
|
|
font-size: 10px;
|
|
color: $fr-muted;
|
|
}
|
|
|
|
.fr-media-badge {
|
|
background: $fr-page;
|
|
border: 1px solid $fr-border;
|
|
padding: 1px 6px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
.fr-media-thumbs {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px;
|
|
}
|
|
|
|
.fr-media-thumb {
|
|
width: 48px;
|
|
height: 48px;
|
|
object-fit: cover;
|
|
border-radius: 4px;
|
|
border: 1px solid $fr-border;
|
|
}
|
|
|
|
// ----- Runner (Phase 3) styling -----
|
|
.fr-runner-wrap {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: calc(100vh - 46px);
|
|
background: $fr-page;
|
|
}
|
|
|
|
.fr-runner-header {
|
|
background: $fr-panel;
|
|
border-bottom: 1px solid $fr-border;
|
|
padding: 10px 18px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
.fr-runner-title { font-size: 15px; font-weight: 700; }
|
|
}
|
|
|
|
.fr-runner-body {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 30px 20px;
|
|
}
|
|
|
|
.fr-runner-card {
|
|
max-width: 760px;
|
|
margin: 0 auto;
|
|
background: $fr-card;
|
|
border: 1px solid $fr-border;
|
|
border-radius: 10px;
|
|
padding: 30px;
|
|
box-shadow: 0 2px 12px rgba(0,0,0,0.08);
|
|
}
|
|
|
|
.fr-runner-card h2 {
|
|
font-size: 24px;
|
|
font-weight: 700;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.fr-runner-content {
|
|
font-size: 16px;
|
|
line-height: 1.5;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.fr-runner-media {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: 10px;
|
|
margin: 16px 0;
|
|
}
|
|
.fr-runner-media img,
|
|
.fr-runner-media video {
|
|
width: 100%;
|
|
border-radius: 6px;
|
|
border: 1px solid $fr-border;
|
|
}
|
|
|
|
.fr-runner-options {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
margin-top: 18px;
|
|
}
|
|
|
|
.fr-runner-option-btn {
|
|
text-align: left;
|
|
padding: 14px 18px;
|
|
font-size: 15px;
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.fr-runner-note {
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.fr-runner-transcript {
|
|
margin-top: 24px;
|
|
padding: 12px 16px;
|
|
background: $fr-page;
|
|
border-radius: 6px;
|
|
font-size: 12px;
|
|
color: $fr-muted;
|
|
border: 1px solid $fr-border;
|
|
}
|
|
|
|
// Tree-view (canvas) read-only highlights for the runner toggle.
|
|
.fr-runner-tree .fr-node-card { opacity: 0.55; }
|
|
.fr-runner-tree .fr-visited .fr-node-card { opacity: 1; outline: 2px solid #16a34a; }
|
|
.fr-runner-tree .fr-current .fr-node-card { opacity: 1; outline: 3px solid #facc15; }
|