Skip to content
This repository was archived by the owner on Jun 26, 2020. It is now read-only.

Commit c49b573

Browse files
authored
Merge pull request #114 from ckeditor/t/98
Other: The `<h1>` elements are now converted to `<heading1>` elements instead of being converted to `<paragraph>`s by default. Closes #98. Closes ckeditor/ckeditor5-paste-from-office#2.
2 parents a4b1a93 + 1bd3f38 commit c49b573

File tree

4 files changed

+111
-2
lines changed

4 files changed

+111
-2
lines changed

docs/features/headings.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ The {@link module:heading/heading~Heading} feature enables support for headings.
1414

1515
## Heading levels
1616

17-
By default this feature is configured to support `<h2>`, `<h3>` and `<h4>` elements which are named: "Heading 1", "Heading 2" and "Heading 3", respectively. The rationale behind starting from `<h2>` is that `<h1>` should be reserved for the page's main title and the page content will usually start from `<h2>`.
17+
By default this feature is configured to support `<h2>`, `<h3>` and `<h4>` elements which are named: "Heading 1", "Heading 2" and "Heading 3", respectively. Additionally, the `<h1>` element is supported and is converted to `<h2>` ("Heading 1") with a low priority by default.
18+
19+
The rationale behind such behaviour is that `<h1>` should be reserved for the page's main title and the page content will usually start from `<h2>`. However, when content with `<h1>` elements is used in the editor (set or pasted) it makes more sense from the user perspective and content semantics to convert `<h1>` elements into `<h2>` instead of paragraphs (`<p>`).
1820

1921
<info-box hint>
2022
You can read more about why the editor should not create `<h1>` elements in the [Headings section of Editor Recommendations](http://ckeditor.github.io/editor-recommendations/features/headings.html).

src/headingediting.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
1111
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
1212
import HeadingCommand from './headingcommand';
1313

14+
import priorities from '@ckeditor/ckeditor5-utils/src/priorities';
15+
import { upcastElementToElement } from '@ckeditor/ckeditor5-engine/src/conversion/upcast-converters';
16+
1417
const defaultModelElement = 'paragraph';
1518

1619
/**
@@ -67,6 +70,8 @@ export default class HeadingEditing extends Plugin {
6770
}
6871
}
6972

73+
this._addDefaultH1Conversion( editor );
74+
7075
// Register the heading command for this option.
7176
editor.commands.add( 'heading', new HeadingCommand( editor, modelElements ) );
7277
}
@@ -92,4 +97,20 @@ export default class HeadingEditing extends Plugin {
9297
} );
9398
}
9499
}
100+
101+
/**
102+
* Adds default conversion for `h1` -> `heading1` with a low priority.
103+
*
104+
* @private
105+
* @param {module:core/editor/editor~Editor} editor Editor instance on which to add the `h1` conversion.
106+
*/
107+
_addDefaultH1Conversion( editor ) {
108+
editor.conversion.for( 'upcast' ).add( upcastElementToElement( {
109+
model: 'heading1',
110+
view: 'h1',
111+
// With a `low` priority, `paragraph` plugin autoparagraphing mechanism is executed. Make sure
112+
// this listener is called before it. If not, `h1` will be transformed into a paragraph.
113+
converterPriority: priorities.get( 'low' ) + 1
114+
} ) );
115+
}
95116
}

tests/headingediting.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import HeadingCommand from '../src/headingcommand';
88
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
99
import ParagraphCommand from '@ckeditor/ckeditor5-paragraph/src/paragraphcommand';
1010
import VirtualTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/virtualtesteditor';
11+
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
1112
import { getData } from '@ckeditor/ckeditor5-engine/src/dev-utils/model';
1213

1314
describe( 'HeadingEditing', () => {
@@ -73,6 +74,13 @@ describe( 'HeadingEditing', () => {
7374
expect( editor.getData() ).to.equal( '<h4>foobar</h4>' );
7475
} );
7576

77+
it( 'should convert h1 to heading1 using default, low-priority converter', () => {
78+
editor.setData( '<h1>foobar</h1>' );
79+
80+
expect( getData( model, { withoutSelection: true } ) ).to.equal( '<heading1>foobar</heading1>' );
81+
expect( editor.getData() ).to.equal( '<h2>foobar</h2>' );
82+
} );
83+
7684
describe( 'user defined', () => {
7785
beforeEach( () => {
7886
return VirtualTestEditor
@@ -116,6 +124,83 @@ describe( 'HeadingEditing', () => {
116124
} );
117125
} );
118126

127+
describe( 'default h1 conversion', () => {
128+
let addDefaultConversionSpy;
129+
130+
testUtils.createSinonSandbox();
131+
132+
beforeEach( () => {
133+
addDefaultConversionSpy = testUtils.sinon.spy( HeadingEditing.prototype, '_addDefaultH1Conversion' );
134+
} );
135+
136+
it( 'should define the default h1 to heading1 converter' +
137+
'when heading.options is not specified and apply it during conversions', () => {
138+
return VirtualTestEditor
139+
.create( {
140+
plugins: [ HeadingEditing ]
141+
} )
142+
.then( editor => {
143+
expect( addDefaultConversionSpy.callCount ).to.equal( 1 );
144+
145+
editor.setData( '<h1>Foo</h1><h2>Bar</h2><p>Baz</p>' );
146+
147+
expect( getData( editor.model, { withoutSelection: true } ) )
148+
.to.equal( '<heading1>Foo</heading1><heading1>Bar</heading1><paragraph>Baz</paragraph>' );
149+
150+
expect( editor.getData() ).to.equal( '<h2>Foo</h2><h2>Bar</h2><p>Baz</p>' );
151+
} );
152+
} );
153+
154+
it( 'should define the default h1 to heading1 converter' +
155+
'when heading.options is specified and apply it during conversions', () => {
156+
const options = [
157+
{ model: 'heading1', view: 'h3' },
158+
{ model: 'heading2', view: 'h4' }
159+
];
160+
161+
return VirtualTestEditor
162+
.create( {
163+
plugins: [ HeadingEditing ],
164+
heading: { options }
165+
} )
166+
.then( editor => {
167+
expect( addDefaultConversionSpy.callCount ).to.equal( 1 );
168+
169+
editor.setData( '<h1>Foo</h1><h3>Bar</h3><h4>Baz</h4><h2>Bax</h2>' );
170+
171+
expect( getData( editor.model, { withoutSelection: true } ) )
172+
.to.equal( '<heading1>Foo</heading1><heading1>Bar</heading1><heading2>Baz</heading2><paragraph>Bax</paragraph>' );
173+
174+
expect( editor.getData() ).to.equal( '<h3>Foo</h3><h3>Bar</h3><h4>Baz</h4><p>Bax</p>' );
175+
} );
176+
} );
177+
178+
it( 'should define the default h1 to heading1 converter' +
179+
'when heading.options is specified with h1 but not apply it during conversions', () => {
180+
const options = [
181+
{ model: 'heading1', view: 'h2' },
182+
{ model: 'heading2', view: 'h1' },
183+
{ model: 'heading3', view: 'h3' }
184+
];
185+
186+
return VirtualTestEditor
187+
.create( {
188+
plugins: [ HeadingEditing ],
189+
heading: { options }
190+
} )
191+
.then( editor => {
192+
expect( addDefaultConversionSpy.callCount ).to.equal( 1 );
193+
194+
editor.setData( '<h1>Foo</h1><h2>Bar</h2><h3>Baz</h3><h4>Bax</h4>' );
195+
196+
expect( getData( editor.model, { withoutSelection: true } ) )
197+
.to.equal( '<heading2>Foo</heading2><heading1>Bar</heading1><heading3>Baz</heading3><paragraph>Bax</paragraph>' );
198+
199+
expect( editor.getData() ).to.equal( '<h1>Foo</h1><h2>Bar</h2><h3>Baz</h3><p>Bax</p>' );
200+
} );
201+
} );
202+
} );
203+
119204
it( 'should not blow up if there\'s no enter command in the editor', () => {
120205
return VirtualTestEditor
121206
.create( {

tests/manual/heading.html

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<div id="editor">
2-
<h2>Heading 1</h2>
2+
<h1>Heading 1 (h1)</h1>
3+
<h2>Heading 1 (h2)</h2>
34
<h3>Heading 2</h3>
45
<h4>Heading 3</h4>
56
<p>Paragraph</p>

0 commit comments

Comments
 (0)