@wordpress/block-library 9.34.1-next.2f1c7c01b.0 → 9.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -0
- package/build/block/edit.js +2 -2
- package/build/block/edit.js.map +2 -2
- package/build/block-keyboard-shortcuts/index.js +17 -7
- package/build/block-keyboard-shortcuts/index.js.map +2 -2
- package/build/cover/deprecated.js +15 -3
- package/build/cover/deprecated.js.map +2 -2
- package/build/cover/edit/inspector-controls.js +1 -1
- package/build/cover/edit/inspector-controls.js.map +2 -2
- package/build/cover/transforms.js +10 -2
- package/build/cover/transforms.js.map +2 -2
- package/build/embed/icons.js +2 -2
- package/build/embed/icons.js.map +2 -2
- package/build/embed/variations.js +3 -3
- package/build/embed/variations.js.map +2 -2
- package/build/heading/index.js +3 -1
- package/build/heading/index.js.map +3 -3
- package/build/heading/transforms.js +10 -3
- package/build/heading/transforms.js.map +2 -2
- package/build/heading/variations.js +55 -0
- package/build/heading/variations.js.map +7 -0
- package/build/html/edit.js +54 -44
- package/build/html/edit.js.map +3 -3
- package/build/html/modal.js +328 -0
- package/build/html/modal.js.map +7 -0
- package/build/html/utils.js +72 -0
- package/build/html/utils.js.map +7 -0
- package/build/navigation-link/edit.js +25 -10
- package/build/navigation-link/edit.js.map +2 -2
- package/build/navigation-link/link-ui/index.js +8 -3
- package/build/navigation-link/link-ui/index.js.map +2 -2
- package/build/navigation-link/shared/controls.js +42 -7
- package/build/navigation-link/shared/controls.js.map +2 -2
- package/build/navigation-link/shared/use-entity-binding.js +31 -2
- package/build/navigation-link/shared/use-entity-binding.js.map +3 -3
- package/build/paragraph/block.json +1 -3
- package/build/paragraph/deprecated.js +65 -12
- package/build/paragraph/deprecated.js.map +2 -2
- package/build/paragraph/edit.js +14 -25
- package/build/paragraph/edit.js.map +2 -2
- package/build/paragraph/index.js +3 -1
- package/build/paragraph/index.js.map +3 -3
- package/build/paragraph/save.js +3 -3
- package/build/paragraph/save.js.map +2 -2
- package/build/paragraph/transforms.js +7 -1
- package/build/paragraph/transforms.js.map +2 -2
- package/build/paragraph/variations.js +57 -0
- package/build/paragraph/variations.js.map +7 -0
- package/build-module/block/edit.js +2 -2
- package/build-module/block/edit.js.map +2 -2
- package/build-module/block-keyboard-shortcuts/index.js +17 -7
- package/build-module/block-keyboard-shortcuts/index.js.map +2 -2
- package/build-module/cover/deprecated.js +15 -3
- package/build-module/cover/deprecated.js.map +2 -2
- package/build-module/cover/edit/inspector-controls.js +1 -1
- package/build-module/cover/edit/inspector-controls.js.map +2 -2
- package/build-module/cover/transforms.js +10 -2
- package/build-module/cover/transforms.js.map +2 -2
- package/build-module/embed/icons.js +2 -2
- package/build-module/embed/icons.js.map +2 -2
- package/build-module/embed/variations.js +3 -3
- package/build-module/embed/variations.js.map +2 -2
- package/build-module/heading/index.js +3 -1
- package/build-module/heading/index.js.map +2 -2
- package/build-module/heading/transforms.js +10 -3
- package/build-module/heading/transforms.js.map +2 -2
- package/build-module/heading/variations.js +34 -0
- package/build-module/heading/variations.js.map +7 -0
- package/build-module/html/edit.js +62 -51
- package/build-module/html/edit.js.map +2 -2
- package/build-module/html/modal.js +304 -0
- package/build-module/html/modal.js.map +7 -0
- package/build-module/html/utils.js +46 -0
- package/build-module/html/utils.js.map +7 -0
- package/build-module/navigation-link/edit.js +25 -10
- package/build-module/navigation-link/edit.js.map +2 -2
- package/build-module/navigation-link/link-ui/index.js +8 -3
- package/build-module/navigation-link/link-ui/index.js.map +2 -2
- package/build-module/navigation-link/shared/controls.js +42 -7
- package/build-module/navigation-link/shared/controls.js.map +2 -2
- package/build-module/navigation-link/shared/use-entity-binding.js +35 -3
- package/build-module/navigation-link/shared/use-entity-binding.js.map +2 -2
- package/build-module/paragraph/block.json +1 -3
- package/build-module/paragraph/deprecated.js +65 -12
- package/build-module/paragraph/deprecated.js.map +2 -2
- package/build-module/paragraph/edit.js +14 -26
- package/build-module/paragraph/edit.js.map +2 -2
- package/build-module/paragraph/index.js +3 -1
- package/build-module/paragraph/index.js.map +2 -2
- package/build-module/paragraph/save.js +3 -3
- package/build-module/paragraph/save.js.map +2 -2
- package/build-module/paragraph/transforms.js +7 -1
- package/build-module/paragraph/transforms.js.map +2 -2
- package/build-module/paragraph/variations.js +36 -0
- package/build-module/paragraph/variations.js.map +7 -0
- package/build-style/accordion-heading/style-rtl.css +19 -3
- package/build-style/accordion-heading/style.css +19 -3
- package/build-style/accordion-panel/style-rtl.css +4 -1
- package/build-style/accordion-panel/style.css +4 -1
- package/build-style/common-rtl.css +3 -3
- package/build-style/common.css +3 -3
- package/build-style/editor-rtl.css +62 -21
- package/build-style/editor.css +62 -21
- package/build-style/embed/style-rtl.css +5 -0
- package/build-style/embed/style.css +5 -0
- package/build-style/html/editor-rtl.css +55 -21
- package/build-style/html/editor.css +55 -21
- package/build-style/navigation-link/editor-rtl.css +7 -0
- package/build-style/navigation-link/editor.css +7 -0
- package/build-style/style-rtl.css +31 -7
- package/build-style/style.css +31 -7
- package/package.json +37 -37
- package/src/accordion-heading/style.scss +40 -7
- package/src/accordion-panel/style.scss +6 -1
- package/src/block/edit.js +2 -2
- package/src/block-keyboard-shortcuts/index.js +23 -9
- package/src/common.scss +6 -5
- package/src/cover/deprecated.js +15 -3
- package/src/cover/edit/inspector-controls.js +1 -1
- package/src/cover/transforms.js +10 -2
- package/src/embed/icons.js +2 -4
- package/src/embed/style.scss +6 -0
- package/src/embed/variations.js +3 -3
- package/src/heading/index.js +2 -0
- package/src/heading/transforms.js +10 -3
- package/src/heading/variations.js +37 -0
- package/src/html/edit.js +62 -56
- package/src/html/editor.scss +69 -10
- package/src/html/modal.js +290 -0
- package/src/html/test/utils.js +234 -0
- package/src/html/utils.js +75 -0
- package/src/navigation-link/edit.js +44 -13
- package/src/navigation-link/editor.scss +7 -0
- package/src/navigation-link/index.php +65 -2
- package/src/navigation-link/link-ui/index.js +9 -8
- package/src/navigation-link/shared/controls.js +70 -12
- package/src/navigation-link/shared/test/controls.js +5 -0
- package/src/navigation-link/shared/test/use-entity-binding.js +14 -1
- package/src/navigation-link/shared/use-entity-binding.js +57 -9
- package/src/paragraph/block.json +1 -3
- package/src/paragraph/deprecated.js +87 -20
- package/src/paragraph/edit.js +7 -18
- package/src/paragraph/edit.native.js +18 -6
- package/src/paragraph/index.js +2 -0
- package/src/paragraph/save.js +4 -3
- package/src/paragraph/test/edit.native.js +5 -5
- package/src/paragraph/transforms.js +7 -1
- package/src/paragraph/variations.js +39 -0
- package/tsconfig.tsbuildinfo +1 -1
package/src/html/editor.scss
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
@use "@wordpress/base-styles/mixins" as *;
|
|
2
|
+
@use "@wordpress/base-styles/variables" as *;
|
|
3
|
+
@use "@wordpress/base-styles/colors" as *;
|
|
2
4
|
|
|
3
5
|
.block-library-html__edit {
|
|
4
6
|
.block-library-html__preview-overlay {
|
|
@@ -8,16 +10,73 @@
|
|
|
8
10
|
top: 0;
|
|
9
11
|
left: 0;
|
|
10
12
|
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Modal styles
|
|
16
|
+
.block-library-html__modal {
|
|
17
|
+
// Make modal content scrollable
|
|
18
|
+
.components-modal__content {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
padding: 0;
|
|
22
|
+
min-height: 70vh;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.components-modal__children-container {
|
|
26
|
+
height: 100%;
|
|
27
|
+
padding: $grid-unit-20;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.block-library-html__modal-tabs {
|
|
32
|
+
height: 100%;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.block-library-html__modal-tab {
|
|
36
|
+
height: 100%;
|
|
37
|
+
display: flex;
|
|
38
|
+
flex-direction: column;
|
|
39
|
+
margin: 0;
|
|
40
|
+
box-sizing: border-box;
|
|
41
|
+
border: 1px solid $gray-200;
|
|
42
|
+
border-radius: 2px;
|
|
43
|
+
padding: $grid-unit-20;
|
|
44
|
+
font-family: $editor-html-font;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.block-library-html__modal-editor {
|
|
48
|
+
width: 100%;
|
|
49
|
+
height: 100%;
|
|
50
|
+
flex: 1;
|
|
51
|
+
// Reset textarea styles to inherit from pre
|
|
52
|
+
border: none;
|
|
53
|
+
background: transparent;
|
|
54
|
+
padding: 0;
|
|
55
|
+
font-family: inherit;
|
|
56
|
+
font-size: inherit;
|
|
57
|
+
line-height: inherit;
|
|
58
|
+
color: inherit;
|
|
59
|
+
resize: none;
|
|
60
|
+
// HTML input is always LTR regardless of language.
|
|
61
|
+
/*rtl:ignore*/
|
|
62
|
+
direction: ltr;
|
|
63
|
+
overflow-x: auto;
|
|
64
|
+
box-sizing: border-box;
|
|
11
65
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
display: block;
|
|
16
|
-
box-sizing: border-box;
|
|
17
|
-
max-height: 250px;
|
|
18
|
-
@include editor-input-reset();
|
|
19
|
-
// HTML input is always LTR regardless of language.
|
|
20
|
-
/*rtl:ignore*/
|
|
21
|
-
direction: ltr;
|
|
66
|
+
&:focus {
|
|
67
|
+
outline: none;
|
|
68
|
+
box-shadow: none;
|
|
22
69
|
}
|
|
23
70
|
}
|
|
71
|
+
|
|
72
|
+
.block-library-html__preview {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
padding: $grid-unit-60;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.block-library-html__modal-actions {
|
|
80
|
+
margin-top: $grid-unit-20;
|
|
81
|
+
padding: 0 $grid-unit-40 $grid-unit-40;
|
|
82
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WordPress dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { __ } from '@wordpress/i18n';
|
|
5
|
+
import { useState, useMemo } from '@wordpress/element';
|
|
6
|
+
import { useSelect } from '@wordpress/data';
|
|
7
|
+
import {
|
|
8
|
+
Modal,
|
|
9
|
+
Button,
|
|
10
|
+
Flex,
|
|
11
|
+
privateApis as componentsPrivateApis,
|
|
12
|
+
__experimentalHStack as HStack,
|
|
13
|
+
__experimentalVStack as VStack,
|
|
14
|
+
} from '@wordpress/components';
|
|
15
|
+
import { PlainText, store as blockEditorStore } from '@wordpress/block-editor';
|
|
16
|
+
import { fullscreen, square } from '@wordpress/icons';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Internal dependencies
|
|
20
|
+
*/
|
|
21
|
+
import { unlock } from '../lock-unlock';
|
|
22
|
+
import Preview from './preview';
|
|
23
|
+
import { parseContent, serializeContent } from './utils';
|
|
24
|
+
|
|
25
|
+
const { Tabs } = unlock( componentsPrivateApis );
|
|
26
|
+
|
|
27
|
+
export default function HTMLEditModal( {
|
|
28
|
+
isOpen,
|
|
29
|
+
onRequestClose,
|
|
30
|
+
content,
|
|
31
|
+
setAttributes,
|
|
32
|
+
} ) {
|
|
33
|
+
// Parse content into separate sections and use as initial state
|
|
34
|
+
const { html, css, js } = parseContent( content );
|
|
35
|
+
const [ editedHtml, setEditedHtml ] = useState( html );
|
|
36
|
+
const [ editedCss, setEditedCss ] = useState( css );
|
|
37
|
+
const [ editedJs, setEditedJs ] = useState( js );
|
|
38
|
+
const [ isDirty, setIsDirty ] = useState( false );
|
|
39
|
+
const [ showUnsavedWarning, setShowUnsavedWarning ] = useState( false );
|
|
40
|
+
const [ isFullscreen, setIsFullscreen ] = useState( false );
|
|
41
|
+
|
|
42
|
+
// Check if user has permission to save scripts and get editor styles
|
|
43
|
+
const { canUserUseUnfilteredHTML, editorStyles } = useSelect(
|
|
44
|
+
( select ) => {
|
|
45
|
+
const settings = select( blockEditorStore ).getSettings();
|
|
46
|
+
return {
|
|
47
|
+
canUserUseUnfilteredHTML:
|
|
48
|
+
settings.__experimentalCanUserUseUnfilteredHTML,
|
|
49
|
+
editorStyles: settings.styles,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
[]
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Show JS tab if user has permission OR if block contains JavaScript
|
|
56
|
+
const shouldShowJsTab = canUserUseUnfilteredHTML || js.trim() !== '';
|
|
57
|
+
|
|
58
|
+
// Combine all editor styles to inject into modal
|
|
59
|
+
const styleContent = useMemo( () => {
|
|
60
|
+
if ( ! editorStyles ) {
|
|
61
|
+
return '';
|
|
62
|
+
}
|
|
63
|
+
return editorStyles
|
|
64
|
+
.filter( ( style ) => style.css )
|
|
65
|
+
.map( ( style ) => style.css )
|
|
66
|
+
.join( '\n' );
|
|
67
|
+
}, [ editorStyles ] );
|
|
68
|
+
|
|
69
|
+
if ( ! isOpen ) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const handleHtmlChange = ( value ) => {
|
|
74
|
+
setEditedHtml( value );
|
|
75
|
+
setIsDirty( true );
|
|
76
|
+
};
|
|
77
|
+
const handleCssChange = ( value ) => {
|
|
78
|
+
setEditedCss( value );
|
|
79
|
+
setIsDirty( true );
|
|
80
|
+
};
|
|
81
|
+
const handleJsChange = ( value ) => {
|
|
82
|
+
setEditedJs( value );
|
|
83
|
+
setIsDirty( true );
|
|
84
|
+
};
|
|
85
|
+
const handleUpdate = () => {
|
|
86
|
+
setAttributes( {
|
|
87
|
+
content: serializeContent( {
|
|
88
|
+
html: editedHtml,
|
|
89
|
+
css: editedCss,
|
|
90
|
+
js: editedJs,
|
|
91
|
+
} ),
|
|
92
|
+
} );
|
|
93
|
+
setIsDirty( false );
|
|
94
|
+
};
|
|
95
|
+
const handleCancel = () => {
|
|
96
|
+
setIsDirty( false );
|
|
97
|
+
onRequestClose();
|
|
98
|
+
};
|
|
99
|
+
const handleRequestClose = () => {
|
|
100
|
+
if ( isDirty ) {
|
|
101
|
+
setShowUnsavedWarning( true );
|
|
102
|
+
} else {
|
|
103
|
+
onRequestClose();
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
const handleDiscardChanges = () => {
|
|
107
|
+
setShowUnsavedWarning( false );
|
|
108
|
+
onRequestClose();
|
|
109
|
+
};
|
|
110
|
+
const handleContinueEditing = () => {
|
|
111
|
+
setShowUnsavedWarning( false );
|
|
112
|
+
};
|
|
113
|
+
const handleUpdateAndClose = () => {
|
|
114
|
+
handleUpdate();
|
|
115
|
+
onRequestClose();
|
|
116
|
+
};
|
|
117
|
+
const toggleFullscreen = () => {
|
|
118
|
+
setIsFullscreen( ( prevState ) => ! prevState );
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<>
|
|
123
|
+
<Modal
|
|
124
|
+
title={ __( 'Edit HTML' ) }
|
|
125
|
+
onRequestClose={ handleRequestClose }
|
|
126
|
+
className="block-library-html__modal"
|
|
127
|
+
size="large"
|
|
128
|
+
isDismissible={ false }
|
|
129
|
+
shouldCloseOnClickOutside={ ! isDirty }
|
|
130
|
+
shouldCloseOnEsc={ ! isDirty }
|
|
131
|
+
isFullScreen={ isFullscreen }
|
|
132
|
+
__experimentalHideHeader
|
|
133
|
+
>
|
|
134
|
+
{ styleContent && (
|
|
135
|
+
<style
|
|
136
|
+
dangerouslySetInnerHTML={ { __html: styleContent } }
|
|
137
|
+
/>
|
|
138
|
+
) }
|
|
139
|
+
<Tabs orientation="horizontal" defaultTabId="html">
|
|
140
|
+
<VStack spacing={ 4 } style={ { height: '100%' } }>
|
|
141
|
+
<HStack justify="space-between">
|
|
142
|
+
<div>
|
|
143
|
+
<Tabs.TabList>
|
|
144
|
+
<Tabs.Tab tabId="html">HTML</Tabs.Tab>
|
|
145
|
+
<Tabs.Tab tabId="css">CSS</Tabs.Tab>
|
|
146
|
+
{ shouldShowJsTab && (
|
|
147
|
+
<Tabs.Tab tabId="js">
|
|
148
|
+
{ __( 'JavaScript' ) }
|
|
149
|
+
</Tabs.Tab>
|
|
150
|
+
) }
|
|
151
|
+
</Tabs.TabList>
|
|
152
|
+
</div>
|
|
153
|
+
<div>
|
|
154
|
+
<Button
|
|
155
|
+
__next40pxDefaultSize
|
|
156
|
+
icon={ isFullscreen ? square : fullscreen }
|
|
157
|
+
label={ __( 'Enable/disable fullscreen' ) }
|
|
158
|
+
onClick={ toggleFullscreen }
|
|
159
|
+
variant="tertiary"
|
|
160
|
+
/>
|
|
161
|
+
</div>
|
|
162
|
+
</HStack>
|
|
163
|
+
<HStack
|
|
164
|
+
alignment="stretch"
|
|
165
|
+
justify="flex-start"
|
|
166
|
+
spacing={ 4 }
|
|
167
|
+
className="block-library-html__modal-tabs"
|
|
168
|
+
style={ { flexGrow: 1 } }
|
|
169
|
+
>
|
|
170
|
+
<div style={ { flexGrow: 1 } }>
|
|
171
|
+
<Tabs.TabPanel
|
|
172
|
+
tabId="html"
|
|
173
|
+
focusable={ false }
|
|
174
|
+
className="block-library-html__modal-tab"
|
|
175
|
+
>
|
|
176
|
+
<PlainText
|
|
177
|
+
value={ editedHtml }
|
|
178
|
+
onChange={ handleHtmlChange }
|
|
179
|
+
placeholder={ __( 'Write HTML…' ) }
|
|
180
|
+
aria-label={ __( 'HTML' ) }
|
|
181
|
+
className="block-library-html__modal-editor"
|
|
182
|
+
/>
|
|
183
|
+
</Tabs.TabPanel>
|
|
184
|
+
<Tabs.TabPanel
|
|
185
|
+
tabId="css"
|
|
186
|
+
focusable={ false }
|
|
187
|
+
className="block-library-html__modal-tab"
|
|
188
|
+
>
|
|
189
|
+
<PlainText
|
|
190
|
+
value={ editedCss }
|
|
191
|
+
onChange={ handleCssChange }
|
|
192
|
+
placeholder={ __( 'Write CSS…' ) }
|
|
193
|
+
aria-label={ __( 'CSS' ) }
|
|
194
|
+
className="block-library-html__modal-editor"
|
|
195
|
+
/>
|
|
196
|
+
</Tabs.TabPanel>
|
|
197
|
+
{ shouldShowJsTab && (
|
|
198
|
+
<Tabs.TabPanel
|
|
199
|
+
tabId="js"
|
|
200
|
+
focusable={ false }
|
|
201
|
+
className="block-library-html__modal-tab"
|
|
202
|
+
>
|
|
203
|
+
<PlainText
|
|
204
|
+
value={ editedJs }
|
|
205
|
+
onChange={ handleJsChange }
|
|
206
|
+
placeholder={ __(
|
|
207
|
+
'Write JavaScript…'
|
|
208
|
+
) }
|
|
209
|
+
aria-label={ __( 'JavaScript' ) }
|
|
210
|
+
className="block-library-html__modal-editor"
|
|
211
|
+
/>
|
|
212
|
+
</Tabs.TabPanel>
|
|
213
|
+
) }
|
|
214
|
+
</div>
|
|
215
|
+
<div
|
|
216
|
+
className="block-library-html__preview"
|
|
217
|
+
style={ { width: '50%' } }
|
|
218
|
+
>
|
|
219
|
+
<Preview
|
|
220
|
+
content={ serializeContent( {
|
|
221
|
+
html: editedHtml,
|
|
222
|
+
css: editedCss,
|
|
223
|
+
js: editedJs,
|
|
224
|
+
} ) }
|
|
225
|
+
/>
|
|
226
|
+
</div>
|
|
227
|
+
</HStack>
|
|
228
|
+
<HStack
|
|
229
|
+
alignment="center"
|
|
230
|
+
justify="flex-end"
|
|
231
|
+
spacing={ 4 }
|
|
232
|
+
>
|
|
233
|
+
<Button
|
|
234
|
+
__next40pxDefaultSize
|
|
235
|
+
variant="tertiary"
|
|
236
|
+
onClick={ handleCancel }
|
|
237
|
+
>
|
|
238
|
+
{ __( 'Cancel' ) }
|
|
239
|
+
</Button>
|
|
240
|
+
<Button
|
|
241
|
+
__next40pxDefaultSize
|
|
242
|
+
variant="primary"
|
|
243
|
+
onClick={ handleUpdateAndClose }
|
|
244
|
+
>
|
|
245
|
+
{ __( 'Update' ) }
|
|
246
|
+
</Button>
|
|
247
|
+
</HStack>
|
|
248
|
+
</VStack>
|
|
249
|
+
</Tabs>
|
|
250
|
+
</Modal>
|
|
251
|
+
|
|
252
|
+
{ showUnsavedWarning && (
|
|
253
|
+
<Modal
|
|
254
|
+
title={ __( 'Unsaved changes' ) }
|
|
255
|
+
onRequestClose={ handleContinueEditing }
|
|
256
|
+
size="medium"
|
|
257
|
+
>
|
|
258
|
+
<p>
|
|
259
|
+
{ __(
|
|
260
|
+
'You have unsaved changes. What would you like to do?'
|
|
261
|
+
) }
|
|
262
|
+
</p>
|
|
263
|
+
<Flex direction="row" justify="flex-end" gap={ 2 }>
|
|
264
|
+
<Button
|
|
265
|
+
__next40pxDefaultSize
|
|
266
|
+
variant="secondary"
|
|
267
|
+
onClick={ handleDiscardChanges }
|
|
268
|
+
>
|
|
269
|
+
{ __( 'Discard unsaved changes' ) }
|
|
270
|
+
</Button>
|
|
271
|
+
<Button
|
|
272
|
+
__next40pxDefaultSize
|
|
273
|
+
variant="secondary"
|
|
274
|
+
onClick={ handleContinueEditing }
|
|
275
|
+
>
|
|
276
|
+
{ __( 'Continue editing' ) }
|
|
277
|
+
</Button>
|
|
278
|
+
<Button
|
|
279
|
+
__next40pxDefaultSize
|
|
280
|
+
variant="primary"
|
|
281
|
+
onClick={ handleUpdateAndClose }
|
|
282
|
+
>
|
|
283
|
+
{ __( 'Update and close' ) }
|
|
284
|
+
</Button>
|
|
285
|
+
</Flex>
|
|
286
|
+
</Modal>
|
|
287
|
+
) }
|
|
288
|
+
</>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Internal dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { parseContent, serializeContent } from '../utils';
|
|
5
|
+
|
|
6
|
+
describe( 'core/html', () => {
|
|
7
|
+
describe( 'parseContent()', () => {
|
|
8
|
+
it( 'should parse empty content', () => {
|
|
9
|
+
const result = parseContent( '' );
|
|
10
|
+
expect( result ).toEqual( { html: '', css: '', js: '' } );
|
|
11
|
+
} );
|
|
12
|
+
|
|
13
|
+
it( 'should parse whitespace-only content', () => {
|
|
14
|
+
const result = parseContent( ' \n\t ' );
|
|
15
|
+
expect( result ).toEqual( { html: '', css: '', js: '' } );
|
|
16
|
+
} );
|
|
17
|
+
|
|
18
|
+
it( 'should parse HTML-only content', () => {
|
|
19
|
+
const content = '<p>Hello World</p>';
|
|
20
|
+
const result = parseContent( content );
|
|
21
|
+
expect( result ).toEqual( {
|
|
22
|
+
html: '<p>Hello World</p>',
|
|
23
|
+
css: '',
|
|
24
|
+
js: '',
|
|
25
|
+
} );
|
|
26
|
+
} );
|
|
27
|
+
|
|
28
|
+
it( 'should parse CSS-only content', () => {
|
|
29
|
+
const content =
|
|
30
|
+
'<style data-wp-block-html="css">body { color: red; }</style>';
|
|
31
|
+
const result = parseContent( content );
|
|
32
|
+
expect( result ).toEqual( {
|
|
33
|
+
html: '',
|
|
34
|
+
css: 'body { color: red; }',
|
|
35
|
+
js: '',
|
|
36
|
+
} );
|
|
37
|
+
} );
|
|
38
|
+
|
|
39
|
+
it( 'should parse JavaScript-only content', () => {
|
|
40
|
+
const content =
|
|
41
|
+
'<script data-wp-block-html="js">console.log("hello");</script>';
|
|
42
|
+
const result = parseContent( content );
|
|
43
|
+
expect( result ).toEqual( {
|
|
44
|
+
html: '',
|
|
45
|
+
css: '',
|
|
46
|
+
js: 'console.log("hello");',
|
|
47
|
+
} );
|
|
48
|
+
} );
|
|
49
|
+
|
|
50
|
+
it( 'should parse content with all three sections', () => {
|
|
51
|
+
const content = `<style data-wp-block-html="css">
|
|
52
|
+
body { color: red; }
|
|
53
|
+
</style>
|
|
54
|
+
|
|
55
|
+
<script data-wp-block-html="js">
|
|
56
|
+
console.log("hello");
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<p>Hello World</p>`;
|
|
60
|
+
const result = parseContent( content );
|
|
61
|
+
expect( result.css ).toBe( 'body { color: red; }' );
|
|
62
|
+
expect( result.js ).toBe( 'console.log("hello");' );
|
|
63
|
+
expect( result.html ).toBe( '<p>Hello World</p>' );
|
|
64
|
+
} );
|
|
65
|
+
|
|
66
|
+
it( 'should ignore unmarked style tags', () => {
|
|
67
|
+
const content = `<style>body { color: blue; }</style>
|
|
68
|
+
<style data-wp-block-html="css">body { color: red; }</style>
|
|
69
|
+
<p>Test</p>`;
|
|
70
|
+
const result = parseContent( content );
|
|
71
|
+
expect( result.css ).toBe( 'body { color: red; }' );
|
|
72
|
+
expect( result.html ).toContain(
|
|
73
|
+
'<style>body { color: blue; }</style>'
|
|
74
|
+
);
|
|
75
|
+
} );
|
|
76
|
+
|
|
77
|
+
it( 'should ignore unmarked script tags', () => {
|
|
78
|
+
const content = `<script>alert("unmarked");</script>
|
|
79
|
+
<script data-wp-block-html="js">console.log("marked");</script>
|
|
80
|
+
<p>Test</p>`;
|
|
81
|
+
const result = parseContent( content );
|
|
82
|
+
expect( result.js ).toBe( 'console.log("marked");' );
|
|
83
|
+
expect( result.html ).toContain(
|
|
84
|
+
'<script>alert("unmarked");</script>'
|
|
85
|
+
);
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
it( 'should handle multiple marked style tags (takes first)', () => {
|
|
89
|
+
const content = `<style data-wp-block-html="css">first</style>
|
|
90
|
+
<style data-wp-block-html="css">second</style>`;
|
|
91
|
+
const result = parseContent( content );
|
|
92
|
+
expect( result.css ).toBe( 'first' );
|
|
93
|
+
expect( result.html ).toContain( 'second' );
|
|
94
|
+
} );
|
|
95
|
+
|
|
96
|
+
it( 'should handle multiple marked script tags (takes first)', () => {
|
|
97
|
+
const content = `<script data-wp-block-html="js">first</script>
|
|
98
|
+
<script data-wp-block-html="js">second</script>`;
|
|
99
|
+
const result = parseContent( content );
|
|
100
|
+
expect( result.js ).toBe( 'first' );
|
|
101
|
+
expect( result.html ).toContain( 'second' );
|
|
102
|
+
} );
|
|
103
|
+
|
|
104
|
+
it( 'should trim whitespace from extracted sections', () => {
|
|
105
|
+
const content = `<style data-wp-block-html="css">
|
|
106
|
+
|
|
107
|
+
body { color: red; }
|
|
108
|
+
|
|
109
|
+
</style>
|
|
110
|
+
|
|
111
|
+
<script data-wp-block-html="js">
|
|
112
|
+
|
|
113
|
+
console.log("test");
|
|
114
|
+
|
|
115
|
+
</script>
|
|
116
|
+
|
|
117
|
+
<p>Test</p> `;
|
|
118
|
+
const result = parseContent( content );
|
|
119
|
+
expect( result.css ).toBe( 'body { color: red; }' );
|
|
120
|
+
expect( result.js ).toBe( 'console.log("test");' );
|
|
121
|
+
expect( result.html ).toBe( '<p>Test</p>' );
|
|
122
|
+
} );
|
|
123
|
+
|
|
124
|
+
it( 'should handle malformed HTML gracefully', () => {
|
|
125
|
+
const content = '<p>Unclosed tag<div>Test</p>';
|
|
126
|
+
const result = parseContent( content );
|
|
127
|
+
expect( result ).toHaveProperty( 'html' );
|
|
128
|
+
expect( result ).toHaveProperty( 'css' );
|
|
129
|
+
expect( result ).toHaveProperty( 'js' );
|
|
130
|
+
} );
|
|
131
|
+
} );
|
|
132
|
+
|
|
133
|
+
describe( 'serializeContent()', () => {
|
|
134
|
+
it( 'should serialize empty sections', () => {
|
|
135
|
+
const result = serializeContent( { html: '', css: '', js: '' } );
|
|
136
|
+
expect( result ).toBe( '' );
|
|
137
|
+
} );
|
|
138
|
+
|
|
139
|
+
it( 'should serialize HTML-only', () => {
|
|
140
|
+
const result = serializeContent( {
|
|
141
|
+
html: '<p>Hello World</p>',
|
|
142
|
+
css: '',
|
|
143
|
+
js: '',
|
|
144
|
+
} );
|
|
145
|
+
expect( result ).toBe( '<p>Hello World</p>' );
|
|
146
|
+
} );
|
|
147
|
+
|
|
148
|
+
it( 'should serialize CSS-only', () => {
|
|
149
|
+
const result = serializeContent( {
|
|
150
|
+
html: '',
|
|
151
|
+
css: 'body { color: red; }',
|
|
152
|
+
js: '',
|
|
153
|
+
} );
|
|
154
|
+
expect( result ).toBe(
|
|
155
|
+
'<style data-wp-block-html="css">\nbody { color: red; }\n</style>'
|
|
156
|
+
);
|
|
157
|
+
} );
|
|
158
|
+
|
|
159
|
+
it( 'should serialize JavaScript-only', () => {
|
|
160
|
+
const result = serializeContent( {
|
|
161
|
+
html: '',
|
|
162
|
+
css: '',
|
|
163
|
+
js: 'console.log("test");',
|
|
164
|
+
} );
|
|
165
|
+
expect( result ).toBe(
|
|
166
|
+
'<script data-wp-block-html="js">\nconsole.log("test");\n</script>'
|
|
167
|
+
);
|
|
168
|
+
} );
|
|
169
|
+
|
|
170
|
+
it( 'should serialize all three sections in correct order', () => {
|
|
171
|
+
const result = serializeContent( {
|
|
172
|
+
html: '<p>Hello</p>',
|
|
173
|
+
css: 'body { color: red; }',
|
|
174
|
+
js: 'console.log("test");',
|
|
175
|
+
} );
|
|
176
|
+
expect( result ).toBe(
|
|
177
|
+
'<style data-wp-block-html="css">\nbody { color: red; }\n</style>\n\n<script data-wp-block-html="js">\nconsole.log("test");\n</script>\n\n<p>Hello</p>'
|
|
178
|
+
);
|
|
179
|
+
} );
|
|
180
|
+
|
|
181
|
+
it( 'should ignore whitespace-only sections', () => {
|
|
182
|
+
const result = serializeContent( {
|
|
183
|
+
html: '<p>Test</p>',
|
|
184
|
+
css: ' \n ',
|
|
185
|
+
js: '\t\t',
|
|
186
|
+
} );
|
|
187
|
+
expect( result ).toBe( '<p>Test</p>' );
|
|
188
|
+
} );
|
|
189
|
+
|
|
190
|
+
it( 'should handle missing properties gracefully', () => {
|
|
191
|
+
const result = serializeContent( {} );
|
|
192
|
+
expect( result ).toBe( '' );
|
|
193
|
+
} );
|
|
194
|
+
|
|
195
|
+
it( 'should handle undefined values', () => {
|
|
196
|
+
const result = serializeContent( {
|
|
197
|
+
html: undefined,
|
|
198
|
+
css: undefined,
|
|
199
|
+
js: undefined,
|
|
200
|
+
} );
|
|
201
|
+
expect( result ).toBe( '' );
|
|
202
|
+
} );
|
|
203
|
+
} );
|
|
204
|
+
|
|
205
|
+
describe( 'round-trip serialization', () => {
|
|
206
|
+
it( 'should maintain HTML-only content', () => {
|
|
207
|
+
const original = '<p>Hello World</p>';
|
|
208
|
+
const parsed = parseContent( original );
|
|
209
|
+
const serialized = serializeContent( parsed );
|
|
210
|
+
expect( serialized ).toBe( original );
|
|
211
|
+
} );
|
|
212
|
+
|
|
213
|
+
it( 'should maintain content with all sections', () => {
|
|
214
|
+
const original =
|
|
215
|
+
'<style data-wp-block-html="css">\nbody { color: red; }\n</style>\n\n<script data-wp-block-html="js">\nconsole.log("test");\n</script>\n\n<p>Hello</p>';
|
|
216
|
+
const parsed = parseContent( original );
|
|
217
|
+
const serialized = serializeContent( parsed );
|
|
218
|
+
expect( serialized ).toBe( original );
|
|
219
|
+
} );
|
|
220
|
+
|
|
221
|
+
it( 'should maintain complex HTML structures', () => {
|
|
222
|
+
const sections = {
|
|
223
|
+
html: '<div><h1>Title</h1><p>Paragraph</p></div>',
|
|
224
|
+
css: 'h1 { font-size: 2em; }',
|
|
225
|
+
js: 'document.addEventListener("load", () => {});',
|
|
226
|
+
};
|
|
227
|
+
const serialized = serializeContent( sections );
|
|
228
|
+
const parsed = parseContent( serialized );
|
|
229
|
+
expect( parsed.html ).toBe( sections.html );
|
|
230
|
+
expect( parsed.css ).toBe( sections.css );
|
|
231
|
+
expect( parsed.js ).toBe( sections.js );
|
|
232
|
+
} );
|
|
233
|
+
} );
|
|
234
|
+
} );
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses content string into separate HTML, CSS, and JS sections.
|
|
3
|
+
*
|
|
4
|
+
* Extracts CSS from <style data-wp-block-html="css"> tags and
|
|
5
|
+
* JavaScript from <script data-wp-block-html="js"> tags.
|
|
6
|
+
* Everything else is treated as HTML.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} content - The combined content string
|
|
9
|
+
* @return {Object} Object with html, css, and js properties
|
|
10
|
+
*/
|
|
11
|
+
export function parseContent( content = '' ) {
|
|
12
|
+
if ( ! content || ! content.trim() ) {
|
|
13
|
+
return { html: '', css: '', js: '' };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Create a temporary document to parse HTML safely
|
|
17
|
+
const doc = document.implementation.createHTMLDocument( '' );
|
|
18
|
+
doc.body.innerHTML = content;
|
|
19
|
+
|
|
20
|
+
// Extract CSS from marked style tag
|
|
21
|
+
const styleTag = doc.body.querySelector(
|
|
22
|
+
'style[data-wp-block-html="css"]'
|
|
23
|
+
);
|
|
24
|
+
const css = styleTag ? styleTag.textContent.trim() : '';
|
|
25
|
+
if ( styleTag ) {
|
|
26
|
+
styleTag.remove();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Extract JS from marked script tag
|
|
30
|
+
const scriptTag = doc.body.querySelector(
|
|
31
|
+
'script[data-wp-block-html="js"]'
|
|
32
|
+
);
|
|
33
|
+
const js = scriptTag ? scriptTag.textContent.trim() : '';
|
|
34
|
+
if ( scriptTag ) {
|
|
35
|
+
scriptTag.remove();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Everything else is HTML
|
|
39
|
+
const html = doc.body.innerHTML.trim();
|
|
40
|
+
|
|
41
|
+
return { html, css, js };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Serializes HTML, CSS, and JS into a single content string.
|
|
46
|
+
*
|
|
47
|
+
* Creates marked <style> and <script> tags for CSS and JS sections,
|
|
48
|
+
* then appends the HTML content.
|
|
49
|
+
*
|
|
50
|
+
* @param {Object} sections Object with html, css, and js properties
|
|
51
|
+
* @param {string} sections.html HTML content
|
|
52
|
+
* @param {string} sections.css CSS content
|
|
53
|
+
* @param {string} sections.js JavaScript content
|
|
54
|
+
* @return {string} Combined content string
|
|
55
|
+
*/
|
|
56
|
+
export function serializeContent( { html = '', css = '', js = '' } ) {
|
|
57
|
+
const parts = [];
|
|
58
|
+
|
|
59
|
+
// Add CSS if present
|
|
60
|
+
if ( css.trim() ) {
|
|
61
|
+
parts.push( `<style data-wp-block-html="css">\n${ css }\n</style>` );
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Add JS if present
|
|
65
|
+
if ( js.trim() ) {
|
|
66
|
+
parts.push( `<script data-wp-block-html="js">\n${ js }\n</script>` );
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Add HTML
|
|
70
|
+
if ( html.trim() ) {
|
|
71
|
+
parts.push( html );
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return parts.join( '\n\n' );
|
|
75
|
+
}
|