@@ -33,22 +33,29 @@ export class SavePlugin extends Plugin {
33
33
} ;
34
34
35
35
async save ( ) {
36
+ // TODO: implement the "group by" feature for save
36
37
const proms = [ ] ;
37
38
for ( const fn of this . getResource ( "before_save_handlers" ) ) {
38
39
proms . push ( fn ( ) ) ;
39
40
}
40
41
await Promise . all ( proms ) ;
41
- const saveProms = [ ...this . editable . querySelectorAll ( ".o_dirty" ) ] . map ( async ( dirtyEl ) => {
42
- dirtyEl . classList . remove ( "o_dirty" ) ;
43
- const cleanedEl = dirtyEl . cloneNode ( true ) ;
44
- this . dispatchTo ( "clean_for_save_handlers" , { root : cleanedEl } ) ;
45
-
46
- if ( this . config . isTranslation ) {
47
- await this . saveTranslationElement ( cleanedEl ) ;
48
- } else {
49
- await this . saveView ( cleanedEl ) ;
42
+ const dirtyEls = [ ] ;
43
+ for ( const getDirtyElsNotInDom of this . getResource ( "get_dirty_els_not_in_dom" ) ) {
44
+ dirtyEls . push ( ...getDirtyElsNotInDom ( ) ) ;
45
+ }
46
+ const saveProms = [ ...dirtyEls , ...this . editable . querySelectorAll ( ".o_dirty" ) ] . map (
47
+ async ( dirtyEl ) => {
48
+ dirtyEl . classList . remove ( "o_dirty" ) ;
49
+ const cleanedEl = dirtyEl . cloneNode ( true ) ;
50
+ this . dispatchTo ( "clean_for_save_handlers" , { root : cleanedEl } ) ;
51
+
52
+ if ( this . config . isTranslation ) {
53
+ await this . saveTranslationElement ( cleanedEl ) ;
54
+ } else {
55
+ await this . saveView ( cleanedEl ) ;
56
+ }
50
57
}
51
- } ) ;
58
+ ) ;
52
59
// used to track dirty out of the editable scope, like header, footer or wrapwrap
53
60
const willSaves = this . getResource ( "save_handlers" ) . map ( ( c ) => c ( ) ) ;
54
61
await Promise . all ( saveProms . concat ( willSaves ) ) ;
@@ -126,7 +133,7 @@ export class SavePlugin extends Plugin {
126
133
if ( el . dataset [ "oeTranslationSourceSha" ] ) {
127
134
const translations = { } ;
128
135
translations [ this . services . website . currentWebsite . metadata . lang ] = {
129
- [ el . dataset [ "oeTranslationSourceSha" ] ] : el . innerHTML ,
136
+ [ el . dataset [ "oeTranslationSourceSha" ] ] : this . getEscapedElement ( el ) . innerHTML ,
130
137
} ;
131
138
return rpc ( "/web_editor/field/translation/update" , {
132
139
model : el . dataset [ "oeModel" ] ,
@@ -139,6 +146,34 @@ export class SavePlugin extends Plugin {
139
146
return this . saveView ( el ) ;
140
147
}
141
148
149
+ getEscapedElement ( el ) {
150
+ const escapedEl = el . cloneNode ( true ) ;
151
+ const allElements = [ escapedEl , ...escapedEl . querySelectorAll ( "*" ) ] ;
152
+ const exclusion = [ ] ;
153
+ for ( const element of allElements ) {
154
+ if (
155
+ element . matches (
156
+ "object,iframe,script,style,[data-oe-model]:not([data-oe-model='ir.ui.view'])"
157
+ )
158
+ ) {
159
+ exclusion . push ( el ) ;
160
+ exclusion . push ( ...el . querySelectorAll ( "*" ) ) ;
161
+ }
162
+ }
163
+ const exclusionSet = new Set ( exclusion ) ;
164
+ const toEscapeEls = allElements . filter ( ( el ) => ! exclusionSet . has ( el ) ) ;
165
+ for ( const toEscapeEl of toEscapeEls ) {
166
+ for ( const child of Array . from ( toEscapeEl . childNodes ) ) {
167
+ if ( child . nodeType === 3 ) {
168
+ const divEl = document . createElement ( "div" ) ;
169
+ divEl . textContent = child . nodeValue ;
170
+ child . nodeValue = divEl . innerHTML ;
171
+ }
172
+ }
173
+ }
174
+ return escapedEl ;
175
+ }
176
+
142
177
/**
143
178
* Handles the flag of the closest savable element to the mutation as dirty
144
179
*
0 commit comments