diff --git a/.travis.yml b/.travis.yml index 1ff046bb416..ea4e098a568 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,12 @@ -sudo: required dist: xenial addons: chrome: stable language: node_js services: - xvfb -cache: - yarn: true node_js: - "12" -cache: -- node_modules +cache: yarn before_install: - export START_TIME=$( date +%s ) - export COVERALLS_SERVICE_JOB_ID=$( TRAVIS_JOB_ID ) @@ -18,20 +14,14 @@ before_install: - npm i -g yarn install: - yarn install -# The "./manual/all-features-dll.js" test requires building DLL. -- yarn run dll:build script: -- node ./scripts/continuous-integration-script.js -- yarn run lint -- yarn run stylelint -- ./scripts/check-manual-tests.sh -r ckeditor5 -f ckeditor5 -- yarn run docs --strict -- 'if [ $TRAVIS_TEST_RESULT -eq 0 ]; then - travis_wait 30 yarn run docs:build-and-publish-nightly; - fi' +- ./scripts/ci/travis-check.js +# "travis_wait" does not work in child processes. Hence, it must be called from the configuration file. +- if [[ $TRAVIS_JOB_TYPE == "Validation" && $TRAVIS_TEST_RESULT -eq 0 ]]; then travis_wait 30 yarn run docs:build-and-publish-nightly; fi after_script: - export END_TIME=$( date +%s ) - ckeditor5-dev-tests-notify-travis-status env: - global: - - secure: RO140EQDHmEOPJPikk8eCY5IdHpnEKGm41p5U1ewAbeZv1DpCG+rSumR2JdYl75kFAaZvCSm1NuVMM+kmYd+/z+LQbKj7QH5G/UHNho3H89blIU6WlJhT0YR5vclm9rvnEvOtxnfODca1Qrw+CaCoJks2o4VYbJB7mOBVNsh7Bc= + jobs: + - TRAVIS_JOB_TYPE=Tests + - TRAVIS_JOB_TYPE=Validation diff --git a/CHANGELOG.md b/CHANGELOG.md index b4b9db529d8..a7ee1a07107 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,13 +5,15 @@ Changelog ### Release highlights -We are happy to announce the release of CKEditor 5 v27.0.0. +We are happy to announce the release of CKEditor 5 v27.0.0 that contains security fixes for multiple packages: `ckeditor5-engine`, `ckeditor5-font`, `ckeditor5-image`, `ckeditor5-list`, `ckeditor5-markdown-gfm`, `ckeditor5-media-embed`, `ckeditor5-paste-from-office`, `ckeditor5-widget`. Even though this is a low impact issue and only affects the victim's browser with no risk of data leakage, an upgrade is highly recommended! You can read more details in the relevant [security advisory](https://github.com/ckeditor/ckeditor5/security/advisories/GHSA-3rh3-wfr4-76mj) and [contact us](https://ckeditor.com/contact/) if you have more questions. + +The CKEditor 5 team would like to thank Yeting Li for recognizing and reporting these vulnerabilities. Starting from this version, collaboration features release notes will be included in the CKEditor 5 changelog. Changes for the previous releases are available on https://ckeditor.com/collaboration/changelog/. This release introduces some new features: -* The new [text part language](https://github.com/ckeditor/ckeditor5/issues/8989) feature allows you to define the language for each passage of content written in multiple languages. This helps satisfy the WCAG Success Criterion 3.1.2 Language of Parts. +* The new [text part language](https://ckeditor.com/docs/ckeditor5/latest/features/language.html) feature allows you to define the language for each passage of content written in multiple languages. This helps satisfy the WCAG Success Criterion 3.1.2 Language of Parts. * Support for [drag and dropping of textual content and block objects](https://ckeditor.com/docs/ckeditor5/latest/features/pasting/drag-drop.html) (like images and tables) within the editor. * Support for [dropping HTML content from outside of the editor](https://ckeditor.com/docs/ckeditor5/latest/features/pasting/drag-drop.html) into the editor. * [Alignment can now be set using classes](https://github.com/ckeditor/ckeditor5/issues/8516). diff --git a/docs/_snippets/builds/frameworks/framework-integration.js b/docs/_snippets/builds/frameworks/framework-integration.js index c17779e555a..adf3be47496 100644 --- a/docs/_snippets/builds/frameworks/framework-integration.js +++ b/docs/_snippets/builds/frameworks/framework-integration.js @@ -5,12 +5,13 @@ /* globals document */ -// Sometimes the request to `badge.fury.io` fails for unknown reason, so ignore it. +// Sometimes the request to external resources (like `badge.fury.io` or `emojics.com`) fails for unknown reasons, +// so ignore all navigation timeouts for framework integration docs. const metaElement = document.createElement( 'meta' ); metaElement.name = 'x-cke-crawler-ignore-patterns'; metaElement.content = JSON.stringify( { - 'request-failure': 'badge.fury.io' + 'navigation-error': 'timeout' } ); document.head.appendChild( metaElement ); diff --git a/docs/_snippets/features/wproofreader.html b/docs/_snippets/features/wproofreader.html index d37be14ca54..88d492d4993 100644 --- a/docs/_snippets/features/wproofreader.html +++ b/docs/_snippets/features/wproofreader.html @@ -1,4 +1,7 @@
Typos hapen. We striving to correct them. Hover on the marked words for instant correction suggestions or click the dialoge icon in the bottom right corner to have the whole text proofread at once.
-You can also paste your own text here to have its spelling and grammar checked.
+Warsaw is the capital adn largest city of Poland. The metropolis stands on the Vistula River in east-central Poland and its population is officially estimatd at 1.8 million residents within a greater metropolitan area of 3.1 million residents, which makes Warsaw the 7th most-populous capital city in the European Union. Warsaw is an alpha-global city, a major international tourist destination, and a significaant cultural, political, and economic hub. Its historical Old Town was designated a UNESCO World Heritage Site.
+ +Warschau ist seit 1596 die Hauptstadt Polens und die flächenmäßig größte sowie mit über 1,75 Mio. Einwonern bevölkerungsreichste Staddt des Landes. Als eines der wichtigsten Verkehrs-, Wirtschafts- und Handelszentren Mittel- und Osteeuropas genießt Warschau große politische und kulturelle Bedetung. In der Stadt befinden sich zahlreiche Institutionen, Universitäten, Theater, Museen und Baudenkmäler.
+ +Варша́ва является столицей и крупнейшим по населению, и территории городом Польшй. Город стал фактической столицей в 1596 году, коггда после пожара в Вавельском замке в Кракове король Сигизмунд III перёнёс свою резиденцию суда, при этом столичный статус города был подтверждён только в Конституции 1791 года. Через город протекает река Висла, разделяющая город приблизительно поровну.
` with the `align` attribute are accepted by the plugin, but the editor always returns the markup in a modern format, so the transformation is one way only. + ## Contribute The source code of the feature is available on GitHub in https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-alignment. diff --git a/packages/ckeditor5-alignment/lang/translations/de-ch.po b/packages/ckeditor5-alignment/lang/translations/de-ch.po new file mode 100644 index 00000000000..ae8b2eae92d --- /dev/null +++ b/packages/ckeditor5-alignment/lang/translations/de-ch.po @@ -0,0 +1,41 @@ +# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved. +# +# !!! IMPORTANT !!! +# +# Before you edit this file, please keep in mind that contributing to the project +# translations is possible ONLY via the Transifex online service. +# +# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5. +# +# To learn more, check out the official contributor's guide: +# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html +# +msgid "" +msgstr "" +"Language-Team: German (Switzerland) (https://www.transifex.com/ckeditor/teams/11143/de_CH/)\n" +"Language: de_CH\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +msgctxt "Toolbar button tooltip for aligning the text to the left." +msgid "Align left" +msgstr "Linksbündig" + +msgctxt "Toolbar button tooltip for aligning the text to the right." +msgid "Align right" +msgstr "Rechtsbündig" + +msgctxt "Toolbar button tooltip for aligning the text to center." +msgid "Align center" +msgstr "Zentriert" + +msgctxt "Toolbar button tooltip for making the text justified." +msgid "Justify" +msgstr "Blocksatz" + +msgctxt "Dropdown button tooltip for the text alignment feature." +msgid "Text alignment" +msgstr "Textausrichtung" + +msgctxt "Label used by assistive technologies describing the text alignment feature toolbar." +msgid "Text alignment toolbar" +msgstr "Text-Ausrichtung Toolbar" diff --git a/packages/ckeditor5-alignment/src/alignment.js b/packages/ckeditor5-alignment/src/alignment.js index 054c653fcb5..5a4d20d410d 100644 --- a/packages/ckeditor5-alignment/src/alignment.js +++ b/packages/ckeditor5-alignment/src/alignment.js @@ -82,10 +82,10 @@ export default class Alignment extends Plugin { * .then( ... ) * .catch( ... ); * - * By default the alignment is set inline using `text-align` CSS property. To further customize the alignment you can - * provide names of classes for each alignment option using `className` property. + * By default the alignment is set inline using the `text-align` CSS property. To further customize the alignment, + * you can provide names of classes for each alignment option using the `className` property. * - * **Note:** Once you define `className` property for one option, you need to specify it for all other options. + * **Note:** Once you define the `className` property for one option, you need to specify it for all other options. * * ClassicEditor * .create( editorElement, { diff --git a/packages/ckeditor5-alignment/src/alignmentediting.js b/packages/ckeditor5-alignment/src/alignmentediting.js index 6da914b9de2..2191de9a044 100644 --- a/packages/ckeditor5-alignment/src/alignmentediting.js +++ b/packages/ckeditor5-alignment/src/alignmentediting.js @@ -72,6 +72,13 @@ export default class AlignmentEditing extends Plugin { editor.conversion.for( 'upcast' ).attributeToAttribute( definition ); } + const upcastCompatibilityDefinitions = buildUpcastCompatibilityDefinitions( optionsToConvert ); + + // Always upcast from deprecated `align` attribute. + for ( const definition of upcastCompatibilityDefinitions ) { + editor.conversion.for( 'upcast' ).attributeToAttribute( definition ); + } + editor.commands.add( 'alignment', new AlignmentCommand( editor ) ); } } @@ -122,6 +129,27 @@ function buildUpcastInlineDefinitions( options ) { return definitions; } +// Prepare upcast definitions for deprecated `align` attribute. +// @private +function buildUpcastCompatibilityDefinitions( options ) { + const definitions = []; + + for ( const { name } of options ) { + definitions.push( { + view: { + key: 'align', + value: name + }, + model: { + key: 'alignment', + value: name + } + } ); + } + + return definitions; +} + // Prepare conversion definitions for upcast and downcast alignment with classes. // @private function buildClassDefinition( options ) { diff --git a/packages/ckeditor5-alignment/tests/alignmentediting.js b/packages/ckeditor5-alignment/tests/alignmentediting.js index 3eacd9dbe65..f807d1e6a80 100644 --- a/packages/ckeditor5-alignment/tests/alignmentediting.js +++ b/packages/ckeditor5-alignment/tests/alignmentediting.js @@ -556,6 +556,78 @@ describe( 'AlignmentEditing', () => { } ); } ); + describe( 'deprecated `align` attribute', () => { + it( 'should support allowed `align` values in LTR content', () => { + const data = '
A
' + + 'B
' + + 'C
' + + 'D
'; + + editor.setData( data ); + + const expectedModelData = 'A
' + + 'B
' + + 'C
' + + 'D
'; + + expect( getModelData( model ) ).to.equal( expectedModelData ); + expect( editor.getData() ).to.equal( expectedData ); + } ); + + it( 'should support allowed `align` values in RTL content', async () => { + const newEditor = await VirtualTestEditor + .create( { + language: { + content: 'ar' + }, + plugins: [ AlignmentEditing, Paragraph ] + } ); + const model = newEditor.model; + const data = 'A
' + + 'B
' + + 'C
' + + 'D
'; + + newEditor.setData( data ); + + const expectedModelData = 'A
' + + 'B
' + + 'C
' + + 'D
'; + + expect( getModelData( model ) ).to.equal( expectedModelData ); + expect( newEditor.getData() ).to.equal( expectedData ); + + return newEditor.destroy(); + } ); + + it( 'should ignore invalid values', () => { + const data = 'A
' + + 'B
'; + + editor.setData( data ); + + const expectedModelData = 'A
' + + 'B
'; + + expect( getModelData( model ) ).to.equal( expectedModelData ); + expect( editor.getData() ).to.equal( expectedData ); + } ); + } ); + describe( 'should be extensible', () => { it( 'converters in the data pipeline', () => { blockDefaultConversion( editor.data.downcastDispatcher ); diff --git a/packages/ckeditor5-alignment/tests/integration.js b/packages/ckeditor5-alignment/tests/integration.js index 7ebb228c963..eff47a4a30a 100644 --- a/packages/ckeditor5-alignment/tests/integration.js +++ b/packages/ckeditor5-alignment/tests/integration.js @@ -95,4 +95,49 @@ describe( 'Alignment integration', () => { ); } ); } ); + + describe( 'compatibility with \'to-model-attribute\' converter', () => { + it( 'should not set the "alignment" attribute if the schema does not allow', () => { + // See: https://github.com/ckeditor/ckeditor5/pull/9249#issuecomment-815658459. + editor.model.schema.register( 'div', { + inheritAllFrom: '$block', + allowAttributes: [ 'customAlignment' ] + } ); + + // Does not allow for setting the "alignment" attribute for `div` elements. + editor.model.schema.addAttributeCheck( ( context, attributeName ) => { + if ( context.endsWith( 'div' ) && attributeName == 'alignment' ) { + return false; + } + } ); + + editor.conversion.elementToElement( { model: 'div', view: 'div' } ); + + editor.conversion.attributeToAttribute( { + model: { + name: 'div', + key: 'customAlignment', + values: [ 'right' ] + }, + view: { + right: { + key: 'style', + value: { + 'text-align': 'right' + } + } + } + } ); + + // Conversion for the `style[text-align]` attribue will be called twice. + // - The first one comes from the AlignmentEditing plugin, + // - The second one from the test. + editor.setData( 'Misquoting is more widespread than one may think. And it may happen to almost anybody. Sometimes it would be an accident, but sometimes you may consider this a conscious act. One of the better known cases is something a Soviet cosmonaut, Yuri Gagarin, was supposed to say — but apparently never said. The infamous quote goes:
+ +++ +I looked and looked but I didn't see God.
+
Let us refer to the Yuri Gagarin's Wikiquote page on that topic and dig further into this particular case study, as it offers a plausible and factual explanation of this mess-up:
+ +++ +Colonel Valentin Petrov stated in 2006 that the cosmonaut never said such words, and that the quote originated from Nikita Khrushchev's speech at the plenum of the Central Committee of the CPSU about the state's anti-religion campaign, saying:
+ +Gagarin flew into space, but didn't see any god there.+ +Gagarin himself was a member of the Russian Orthodox Church.
+
It looks like a cunning rhetoric to support an ideological point of view and you may even suppose the cosmonaut himself was not personally involved in it. Quite honestly, being a believer, he would have a hard time denying the existence of God. It is something Gherman Titov, the second Soviet cosmonaut, would rather say, being an atheist.
+ +It was, in fact, duly noted in The New York Times, dated May 7, 1962:
++ Maj. Gherman S. Titov, the Soviet astronaut, proclaimed his disbelief in God today. He said he saw ++ +no God or angels+ during his seventeen orbits of the earth. +
A similar statement by Titov was also reported by Pavel Barashev and Yuri Dokuchayev, so it seems this was not entirely made up by Khrushchev, but rather reused and falsely attributed to the Earth's first space farer.
+ +Apparently there was more than one source involved in the origin of this misunderstanding. As you can see, sometimes, just like in the case of the notorious "Einstein fish", things can get messed up pretty easily. And it takes a lot of time to debunk them, as there are numerous occurrences of the quote that partially support the case.
+ +This is why you should always check your sources twice and, if necessary, quote them!
+ +' ); // Autoparagraphed. } ); - it( 'should unwrap a blockQuote if it was inserted into another blockQuote', () => { + it( 'should not unwrap a blockQuote if it was inserted into another blockQuote', () => { setModelData( model, '
' ); model.change( writer => { @@ -115,10 +115,10 @@ describe( 'BlockQuoteEditing', () => { writer.insert( bq, root.getChild( 0 ), 1 ); // Insert afterFoo
Foo
. } ); - expect( editor.getData() ).to.equal( '' ); + expect( editor.getData() ).to.equal( 'Foo
Bar
' ); } ); - it( 'should unwrap nested blockQuote if it was wrapped into another blockQuote', () => { + it( 'should not unwrap nested blockQuote if it was wrapped into another blockQuote', () => { setModelData( model, 'Foo
Bar
Foo
' ); + expect( editor.getData() ).to.equal( 'Foo
Bar
' ); } ); it( 'postfixer should do nothing on attribute change', () => { @@ -143,4 +143,44 @@ describe( 'BlockQuoteEditing', () => { expect( editor.getData() ).to.equal( 'Foo
Bar
' ); } ); + + describe( 'nested blockQuote forbidden by custom rule', () => { + // Nested block quotes are supported since https://github.com/ckeditor/ckeditor5/issues/9210, so let's check + // if the editor will not blow up in case nested block quotes are forbidden by custom scheme rule. + beforeEach( () => { + model.schema.addChildCheck( ( ctx, childDef ) => { + if ( ctx.endsWith( 'blockQuote' ) && childDef.name == 'blockQuote' ) { + return false; + } + } ); + } ); + + it( 'should unwrap a blockQuote if it was inserted into another blockQuote', () => { + setModelData( model, 'Foo
' ); + + model.change( writer => { + const root = model.document.getRoot(); + const bq = writer.createElement( 'blockQuote' ); + const p = writer.createElement( 'paragraph' ); + + writer.insertText( 'Bar', p, 0 ); //Foo
Bar
. + writer.insert( p, bq, 0 ); //. + writer.insert( bq, root.getChild( 0 ), 1 ); // Insert afterBar
Foo
. + } ); + + expect( editor.getData() ).to.equal( '' ); + } ); + + it( 'should unwrap nested blockQuote if it was wrapped into another blockQuote', () => { + setModelData( model, 'Foo
Bar
Foo
' ); + } ); + } ); } ); diff --git a/packages/ckeditor5-block-quote/tests/manual/blockquote.html b/packages/ckeditor5-block-quote/tests/manual/blockquote.html index 5bde4c10c2e..b8ea9f6b252 100644 --- a/packages/ckeditor5-block-quote/tests/manual/blockquote.html +++ b/packages/ckeditor5-block-quote/tests/manual/blockquote.html @@ -15,4 +15,11 @@Foo
Bar
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+++Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris.
+++Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
diff --git a/packages/ckeditor5-block-quote/tests/manual/blockquote.md b/packages/ckeditor5-block-quote/tests/manual/blockquote.md index d36ddc8cf97..13392bf0adf 100644 --- a/packages/ckeditor5-block-quote/tests/manual/blockquote.md +++ b/packages/ckeditor5-block-quote/tests/manual/blockquote.md @@ -8,4 +8,5 @@ Check block quote related behaviors: * Backspace, * undo/redo, * applying headings and lists, -* stability when used with nested lists. +* stability when used with nested lists, +* stability when used with nested block quotes. diff --git a/packages/ckeditor5-block-quote/tests/manual/blockquotenonesting.html b/packages/ckeditor5-block-quote/tests/manual/blockquotenonesting.html new file mode 100644 index 00000000000..573ba51c487 --- /dev/null +++ b/packages/ckeditor5-block-quote/tests/manual/blockquotenonesting.html @@ -0,0 +1,20 @@ + + +Nested block quotes (in the data):
+ +++Nulla finibus consequat placerat. Vestibulum id tellus et mauris sagittis tincidunt quis id mauris.
+++Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+++Vestibulum id tellus et mauris sagittis tincidunt quis id mauris. Curabitur consectetur lectus sit amet tellus mattis, non lobortis leo interdum.
+
abc |
abc |
foo();\nbar();
+ * foo();bar();
*
* Sample output:
*
- * `.
- // Read only text nodes.
- const textData = [ ...editingView.createRangeIn( viewChild ) ]
- .filter( current => current.type === 'text' )
- .map( ( { item } ) => item.data )
- .join( '' );
- const fragment = rawSnippetTextToModelDocumentFragment( writer, textData );
-
- writer.append( fragment, codeBlock );
+ conversionApi.convertChildren( viewCodeElement, codeBlock );
// Let's try to insert code block.
if ( !conversionApi.safeInsert( codeBlock, data.modelCursor ) ) {
return;
}
- consumable.consume( viewItem, { name: true } );
- consumable.consume( viewChild, { name: true } );
+ consumable.consume( viewCodeElement, { name: true } );
conversionApi.updateConversionResult( codeBlock, data );
};
}
+
+/**
+ * A view-to-model converter for new line characters in ``.
+ *
+ * Sample input:
+ *
+ * foo();\nbar();
+ *
+ * Sample output:
+ *
+ * foo(); bar();
+ *
+ * @returns {Function} Returns a conversion callback.
+ */
+export function dataViewToModelTextNewlinesInsertion() {
+ return ( evt, data, { consumable, writer } ) => {
+ let position = data.modelCursor;
+
+ // When node is already converted then do nothing.
+ if ( !consumable.test( data.viewItem ) ) {
+ return;
+ }
+
+ // When not inside `codeBlock` then do nothing.
+ if ( !position.findAncestor( 'codeBlock' ) ) {
+ return;
+ }
+
+ consumable.consume( data.viewItem );
+
+ const text = data.viewItem.data;
+ const textLines = text.split( '\n' ).map( data => writer.createText( data ) );
+ const lastLine = textLines[ textLines.length - 1 ];
+
+ for ( const node of textLines ) {
+ writer.insert( node, position );
+ position = position.getShiftedBy( node.offsetSize );
+
+ if ( node !== lastLine ) {
+ const softBreak = writer.createElement( 'softBreak' );
+
+ writer.insert( softBreak, position );
+ position = writer.createPositionAfter( softBreak );
+ }
+ }
+
+ data.modelRange = writer.createRange(
+ data.modelCursor,
+ position
+ );
+ data.modelCursor = position;
+ };
+}
diff --git a/packages/ckeditor5-code-block/src/utils.js b/packages/ckeditor5-code-block/src/utils.js
index 3386a5bce1a..25720b280df 100644
--- a/packages/ckeditor5-code-block/src/utils.js
+++ b/packages/ckeditor5-code-block/src/utils.js
@@ -99,43 +99,6 @@ export function getLeadingWhiteSpaces( textNode ) {
return textNode.data.match( /^(\s*)/ )[ 0 ];
}
-/**
- * For plain text containing the code (a snippet), it returns a document fragment containing
- * model text nodes separated by soft breaks (in place of new line characters "\n"), for instance:
- *
- * Input:
- *
- * "foo()\n
- * bar()"
- *
- * Output:
- *
- *
- * "foo()"
- *
- * "bar()"
- *
- *
- * @param {module:engine/model/writer~Writer} writer
- * @param {String} text The raw code text to be converted.
- * @returns {module:engine/model/documentfragment~DocumentFragment}
- */
-export function rawSnippetTextToModelDocumentFragment( writer, text ) {
- const fragment = writer.createDocumentFragment();
- const textLines = text.split( '\n' ).map( data => writer.createText( data ) );
- const lastLine = textLines[ textLines.length - 1 ];
-
- for ( const node of textLines ) {
- writer.append( node, fragment );
-
- if ( node !== lastLine ) {
- writer.appendElement( 'softBreak', fragment );
- }
- }
-
- return fragment;
-}
-
/**
* For plain text containing the code (a snippet), it returns a document fragment containing
* view text nodes separated by `
` elements (in place of new line characters "\n"), for instance:
diff --git a/packages/ckeditor5-code-block/tests/codeblockediting.js b/packages/ckeditor5-code-block/tests/codeblockediting.js
index 5d78b61873a..c80a93250f8 100644
--- a/packages/ckeditor5-code-block/tests/codeblockediting.js
+++ b/packages/ckeditor5-code-block/tests/codeblockediting.js
@@ -12,6 +12,7 @@ import OutdentCodeBlockCommand from '../src/outdentcodeblockcommand';
import AlignmentEditing from '@ckeditor/ckeditor5-alignment/src/alignmentediting';
import BoldEditing from '@ckeditor/ckeditor5-basic-styles/src/bold/boldediting';
+import CodeEditing from '@ckeditor/ckeditor5-basic-styles/src/code/codeediting';
import Enter from '@ckeditor/ckeditor5-enter/src/enter';
import ShiftEnter from '@ckeditor/ckeditor5-enter/src/shiftenter';
import Paragraph from '@ckeditor/ckeditor5-paragraph/src/paragraph';
@@ -734,6 +735,26 @@ describe( 'CodeBlockEditing', () => {
return editor.destroy();
} );
} );
+
+ it( 'should convert markers inside pre > code', () => {
+ editor.conversion.for( 'editingDowncast' ).markerToElement( { view: 'group', model: 'group' } );
+
+ setModelData( model,
+ '[]Foo '
+ );
+
+ model.change( writer => {
+ const range = model.createRangeIn( model.document.getRoot().getChild( 0 ) );
+
+ writer.addMarker( 'group', { range, usingOperation: false } );
+ } );
+
+ expect( getViewData( view ) ).to.equal(
+ '' +
+ '[] Foo
' +
+ '
'
+ );
+ } );
} );
describe( 'data pipeline m -> v conversion ', () => {
@@ -866,6 +887,50 @@ describe( 'CodeBlockEditing', () => {
return editor.destroy();
} );
} );
+
+ it( 'should convert markers inside pre > code', () => {
+ editor.conversion.for( 'downcast' ).markerToData( { model: 'group' } );
+
+ setModelData( model,
+ '[]Foo '
+ );
+
+ model.change( writer => {
+ const range = model.createRangeIn( model.document.getRoot().getChild( 0 ) );
+
+ writer.addMarker( 'group:foo:bar:baz', { range, usingOperation: false } );
+ } );
+
+ expect( editor.getData() ).to.equal(
+ '' +
+ '' +
+ ' Foo ' +
+ '
' +
+ '
'
+ );
+ } );
+
+ it( 'should convert markers on a code block', () => {
+ editor.conversion.for( 'downcast' ).markerToData( { model: 'group' } );
+
+ setModelData( model,
+ '[]Foo '
+ );
+
+ model.change( writer => {
+ const range = model.createRangeOn( model.document.getRoot().getChild( 0 ) );
+
+ writer.addMarker( 'group:foo:bar:baz', { range, usingOperation: false } );
+ } );
+
+ expect( editor.getData() ).to.equal(
+ '' +
+ '' +
+ 'Foo' +
+ '
' +
+ '
'
+ );
+ } );
} );
describe( 'data pipeline v -> m conversion ', () => {
@@ -948,7 +1013,53 @@ describe( 'CodeBlockEditing', () => {
expect( getModelData( model ) ).to.equal( '[]Foo\'s&"bar"
' );
} );
- it( 'should be overridable', () => {
+ it( 'should preserve markers inside pre > code', () => {
+ editor.conversion.for( 'upcast' ).dataToMarker( { view: 'group' } );
+
+ editor.setData(
+ '' +
+ '' +
+ '' +
+ ' ' +
+ 'Bar
' +
+ ' ' +
+ '
' +
+ '
' +
+ '
'
+ );
+
+ expect( model.markers.has( 'group:foo:id' ) ).to.be.true;
+
+ const marker = model.markers.get( 'group:foo:id' );
+
+ expect( marker.getStart().path ).to.deep.equal( [ 0, 0 ] );
+ expect( marker.getEnd().path ).to.deep.equal( [ 0, 3 ] );
+
+ expect( getModelData( model ) ).to.equal( '[]Bar ' );
+ } );
+
+ it( 'should preserve markers on a code block', () => {
+ editor.conversion.for( 'upcast' ).dataToMarker( { view: 'group' } );
+
+ editor.setData(
+ '' +
+ '' +
+ 'Foo' +
+ '
' +
+ '
'
+ );
+
+ expect( model.markers.has( 'group:foo:id' ) ).to.be.true;
+
+ const marker = model.markers.get( 'group:foo:id' );
+
+ expect( marker.getStart().path ).to.deep.equal( [ 0 ] );
+ expect( marker.getEnd().path ).to.deep.equal( [ 1 ] );
+
+ expect( getModelData( model ) ).to.equal( '[]Foo ' );
+ } );
+
+ it( 'should be overridable (pre)', () => {
editor.data.upcastDispatcher.on( 'element:pre', ( evt, data, api ) => {
const modelItem = api.writer.createElement( 'codeBlock' );
@@ -965,6 +1076,27 @@ describe( 'CodeBlockEditing', () => {
expect( getModelData( model ) ).to.equal( '[]Hello World! ' );
} );
+ it( 'should be overridable (code)', () => {
+ editor.data.upcastDispatcher.on( 'element:code', ( evt, data, api ) => {
+ if ( !data.viewItem.parent.is( 'element', 'pre' ) ) {
+ return;
+ }
+
+ const modelItem = api.writer.createElement( 'codeBlock' );
+
+ api.writer.appendText( 'Hello World!', modelItem );
+ api.writer.insert( modelItem, data.modelCursor );
+ api.consumable.consume( data.viewItem, { name: true } );
+
+ data.modelCursor = api.writer.createPositionAfter( modelItem );
+ data.modelRange = api.writer.createRangeOn( modelItem );
+ }, { priority: 'high' } );
+
+ editor.setData( 'Foo Bar
' );
+
+ expect( getModelData( model ) ).to.equal( '[]Hello World! ' );
+ } );
+
it( 'should split parents to correctly upcast the code block', () => {
editor.setData( 'foo
x
bar' );
@@ -1007,6 +1139,26 @@ describe( 'CodeBlockEditing', () => {
expect( getModelData( model, { rootName: 'title', withoutSelection: true } ) ).to.equal( '' );
} );
+ it( 'should not conflict with code attribute conversion', async () => {
+ const element = document.createElement( 'div' );
+ document.body.appendChild( element );
+
+ const editor = await ClassicTestEditor.create( element, {
+ plugins: [ CodeEditing, CodeBlockEditing, Paragraph ]
+ } );
+
+ editor.setData( 'foobar
' );
+
+ expect( getModelData( editor.model ) ).to.equal( '[]foobar ' );
+
+ editor.setData( 'foobar
' );
+
+ expect( getModelData( editor.model ) ).to.equal( '<$text code="true">[]foobar$text> ' );
+
+ await editor.destroy();
+ element.remove();
+ } );
+
describe( 'config.codeBlock.languages', () => {
it( 'should be respected when upcasting', () => {
return ClassicTestEditor.create(
diff --git a/packages/ckeditor5-core/lang/translations/de-ch.po b/packages/ckeditor5-core/lang/translations/de-ch.po
new file mode 100644
index 00000000000..a7832447b21
--- /dev/null
+++ b/packages/ckeditor5-core/lang/translations/de-ch.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: German (Switzerland) (https://www.transifex.com/ckeditor/teams/11143/de_CH/)\n"
+"Language: de_CH\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Label for the Save button."
+msgid "Save"
+msgstr "Speichern"
+
+msgctxt "Label for the Cancel button."
+msgid "Cancel"
+msgstr "Abbrechen"
+
+msgctxt "The label used by a button next to the color palette in the color picker that removes the color (resets it to an empty value, example usages in font color or table properties)."
+msgid "Remove color"
+msgstr "Farbe entfernen"
diff --git a/packages/ckeditor5-core/src/multicommand.js b/packages/ckeditor5-core/src/multicommand.js
index 8b3bb06ffde..777e6191d4c 100644
--- a/packages/ckeditor5-core/src/multicommand.js
+++ b/packages/ckeditor5-core/src/multicommand.js
@@ -63,7 +63,7 @@ export default class MultiCommand extends Command {
execute( ...args ) {
const command = this._getFirstEnabledCommand();
- return command.execute( args );
+ return command != null && command.execute( args );
}
/**
diff --git a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js
index 27a4f6c6a9b..76fab4d8cb2 100644
--- a/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js
+++ b/packages/ckeditor5-engine/docs/_snippets/framework/extending-content-custom-element-converter.js
@@ -26,6 +26,11 @@ class InfoBox {
.add( dispatcher => dispatcher.on( 'insert:infoBox', editingDowncastConverter ) );
editor.conversion.for( 'dataDowncast' )
.add( dispatcher => dispatcher.on( 'insert:infoBox', dataDowncastConverter ) );
+
+ // Model to view position mapper is needed since the model content needs to end up in the inner
+ // .
+ editor.editing.mapper.on( 'modelToViewPosition', createModelToViewPositionMapper( editor.editing.view ) );
+ editor.data.mapper.on( 'modelToViewPosition', createModelToViewPositionMapper( editor.editing.view ) );
}
}
@@ -123,12 +128,8 @@ function insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBox
infoBoxContent
);
- // The default mapping between the model and its view representation.
+ // The mapping between the model and its view representation.
conversionApi.mapper.bindElements( data.item, infoBox );
- // However, since the model content needs to end up in the inner
- // , you need to bind one with another overriding
- // a part of the default binding.
- conversionApi.mapper.bindElements( data.item, infoBoxContent );
conversionApi.writer.insert(
conversionApi.mapper.toViewPosition( data.range.start ),
@@ -136,6 +137,49 @@ function insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBox
);
}
+function createModelToViewPositionMapper( view ) {
+ return ( evt, data ) => {
+ const modelPosition = data.modelPosition;
+ const parent = modelPosition.parent;
+
+ // Only mapping of positions that are directly in
+ // the model element should be modified.
+ if ( !parent.is( 'element', 'infoBox' ) ) {
+ return;
+ }
+
+ // Get the mapped view element .
+ const viewElement = data.mapper.toViewElement( parent );
+
+ // Find the in it.
+ const viewContentElement = findContentViewElement( view, viewElement );
+
+ // Translate the model position offset to the view position offset.
+ data.viewPosition = data.mapper.findPositionIn( viewContentElement, modelPosition.offset );
+ };
+}
+
+// Returns the nested in the info box view structure.
+function findContentViewElement( editingView, viewElement ) {
+ for ( const value of editingView.createRangeIn( viewElement ) ) {
+ if ( value.item.is( 'element', 'div' ) && value.item.hasClass( 'info-box-content' ) ) {
+ return value.item;
+ }
+ }
+}
+
+function getTypeFromViewElement( viewElement ) {
+ if ( viewElement.hasClass( 'info-box-info' ) ) {
+ return 'Info';
+ }
+
+ if ( viewElement.hasClass( 'info-box-warning' ) ) {
+ return 'Warning';
+ }
+
+ return 'None';
+}
+
ClassicEditor
.create( document.querySelector( '#editor-custom-element-converter' ), {
cloudServices: CS_CONFIG,
@@ -160,15 +204,3 @@ ClassicEditor
.catch( err => {
console.error( err.stack );
} );
-
-function getTypeFromViewElement( viewElement ) {
- if ( viewElement.hasClass( 'info-box-info' ) ) {
- return 'Info';
- }
-
- if ( viewElement.hasClass( 'info-box-warning' ) ) {
- return 'Warning';
- }
-
- return 'None';
-}
diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/custom-element-conversion.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/custom-element-conversion.md
index a1dbca45cab..394d8c07289 100644
--- a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/custom-element-conversion.md
+++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/custom-element-conversion.md
@@ -251,12 +251,8 @@ function insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBox
infoBoxContent
);
- // The default mapping between the model and its view representation.
+ // The mapping between the model and its view representation.
conversionApi.mapper.bindElements( data.item, infoBox );
- // However, since the model content needs to end up in the inner
- // , you need to bind one with another overriding
- // a part of the default binding.
- conversionApi.mapper.bindElements( data.item, infoBoxContent );
conversionApi.writer.insert(
conversionApi.mapper.toViewPosition( data.range.start ),
@@ -265,7 +261,7 @@ function insertViewElements( data, conversionApi, infoBox, infoBoxTitle, infoBox
}
```
-These two converters need to be plugged as listeners to the {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#insert `DowncastDispatcher#insert` event}:
+These two converters need to be plugged as listeners into the {@link module:engine/conversion/downcastdispatcher~DowncastDispatcher#insert `DowncastDispatcher#insert` event}:
```js
editor.conversion.for( 'editingDowncast' )
@@ -274,6 +270,83 @@ editor.conversion.for( 'dataDowncast' )
.add( dispatcher => dispatcher.on( 'insert:infoBox', dataDowncastConverter ) );
```
+Due to the fact that the info box's view structure is more complex than its model structure, you need to take care of one additional aspect to make the converters work — position mapping.
+
+### The model-to-view position mapping
+
+The downcast converters shown in the previous section will not work correctly yet. This is what the given model would look like, after being downcasted:
+
+```
+ ->
+ ->
+ Foobar -> Foobar
+
->
+ Info
+
+ ->
+```
+
+This is not a correct view structure. The content of the model's `` element ended up directly inside the outer ``. The ``'s content should be inside the ``.
+
+You defined downcast conversion for `` itself, but you need to specify where its content should land in its view structure. By default, it is converted as direct children of `` (as shown in the above snippet) but it should go into ``. To achieve this, you need to register a callback for the {@link module:engine/conversion/mapper~Mapper#event:modelToViewPosition `Mapper#modelToViewPosition`} event, so positions inside the model `` element would map to positions inside the `` view element.
+
+```
+ ->
+ Info
+
+ ->
+ Foobar -> Foobar
+
->
+
+ ->
+```
+
+Such a mapping can be achieved by registering this callback to the {@link module:engine/conversion/mapper~Mapper#event:modelToViewPosition `Mapper#modelToViewPosition`} event:
+
+```js
+function createModelToViewPositionMapper( view ) {
+ return ( evt, data ) => {
+ const modelPosition = data.modelPosition;
+ const parent = modelPosition.parent;
+
+ // Only mapping of positions that are directly in
+ // the model element should be modified.
+ if ( !parent.is( 'element', 'infoBox' ) ) {
+ return;
+ }
+
+ // Get the mapped view element .
+ const viewElement = data.mapper.toViewElement( parent );
+
+ // Find the in it.
+ const viewContentElement = findContentViewElement( view, viewElement );
+
+ // Translate the model position offset to the view position offset.
+ data.viewPosition = data.mapper.findPositionIn( viewContentElement, modelPosition.offset );
+ };
+}
+
+// Returns the nested in the info box view structure.
+function findContentViewElement( editingView, viewElement ) {
+ for ( const value of editingView.createRangeIn( viewElement ) ) {
+ if ( value.item.is( 'element', 'div' ) && value.item.hasClass( 'info-box-content' ) ) {
+ return value.item;
+ }
+ }
+}
+```
+
+It needs to be plugged into the {@link module:engine/conversion/mapper~Mapper#event:modelToViewPosition `Mapper#modelToViewPosition`} event for both downcast pipelines:
+
+```js
+editor.editing.mapper.on( 'modelToViewPosition', createModelToViewPositionMapper( editor.editing.view ) );
+editor.data.mapper.on( 'modelToViewPosition', createModelToViewPositionMapper( editor.editing.view ) );
+```
+
+
+ **Note**: You do not need the reverse position mapping ({@link module:engine/conversion/mapper~Mapper#event:viewToModelPosition from the view to the model}) because the default view-to-model position mapping looks for the {@link module:engine/conversion/mapper~Mapper#findMappedViewAncestor mapped view ancestor} and maps the offset in respect to the model element.
+
+
### Updated plugin code
The updated `InfoBox` plugin that glues the event-based converters together:
@@ -298,6 +371,11 @@ class InfoBox {
.add( dispatcher => dispatcher.on( 'insert:infoBox', editingDowncastConverter ) );
editor.conversion.for( 'dataDowncast' )
.add( dispatcher => dispatcher.on( 'insert:infoBox', dataDowncastConverter ) );
+
+ // Model-to-view position mapper is needed since the model content needs to end up in the inner
+ // .
+ editor.editing.mapper.on( 'modelToViewPosition', createModelToViewPositionMapper( editor.editing.view ) );
+ editor.data.mapper.on( 'modelToViewPosition', createModelToViewPositionMapper( editor.editing.view ) );
}
}
@@ -312,4 +390,8 @@ function editingDowncastConverter() {
function dataDowncastConverter() {
// ...
}
+
+function createModelToViewPositionMapper() {
+ // ...
+}
```
diff --git a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md
index 2a7109e7879..01c38f4ceef 100644
--- a/packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md
+++ b/packages/ckeditor5-engine/docs/framework/guides/deep-dive/schema.md
@@ -431,7 +431,7 @@ The {@link module:engine/model/schema~Schema#checkChild `Schema#checkChild()`} m
These listeners can be added either by listening directly to the {@link module:engine/model/schema~Schema#event:checkChild} event or by using the handy {@link module:engine/model/schema~Schema#addChildCheck `Schema#addChildCheck()`} method.
-For instance, the block quote feature defines such a listener to disallow nested `` structures:
+For instance, to disallow nested `` structures, you can define such a listener:
```js
schema.addChildCheck( ( context, childDefinition ) => {
diff --git a/packages/ckeditor5-engine/src/controller/datacontroller.js b/packages/ckeditor5-engine/src/controller/datacontroller.js
index 358637a3ea4..7889bb5a47b 100644
--- a/packages/ckeditor5-engine/src/controller/datacontroller.js
+++ b/packages/ckeditor5-engine/src/controller/datacontroller.js
@@ -247,14 +247,16 @@ export default class DataController {
// We have no view controller and rendering to DOM in DataController so view.change() block is not used here.
this.downcastDispatcher.convertInsert( modelRange, viewWriter );
- if ( !modelElementOrFragment.is( 'documentFragment' ) ) {
- // Then, if a document element is converted, convert markers.
- // From all document markers, get those, which "intersect" with the converter element.
- const markers = _getMarkersRelativeToElement( modelElementOrFragment );
-
- for ( const [ name, range ] of markers ) {
- this.downcastDispatcher.convertMarkerAdd( name, range, viewWriter );
- }
+ // Convert markers.
+ // For document fragment, simply take the markers assigned to this document fragment.
+ // For model root, all markers in that root will be taken.
+ // For model element, we need to check which markers are intersecting with this element and relatively modify the markers' ranges.
+ const markers = modelElementOrFragment.is( 'documentFragment' ) ?
+ Array.from( modelElementOrFragment.markers ) :
+ _getMarkersRelativeToElement( modelElementOrFragment );
+
+ for ( const [ name, range ] of markers ) {
+ this.downcastDispatcher.convertMarkerAdd( name, range, viewWriter );
}
// Clean `conversionApi`.
diff --git a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
index 5a700112fa1..06499225801 100644
--- a/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
+++ b/packages/ckeditor5-engine/src/conversion/downcastdispatcher.js
@@ -390,8 +390,8 @@ export default class DowncastDispatcher {
* @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document.
*/
convertMarkerAdd( markerName, markerRange, writer ) {
- // Do not convert if range is in graveyard or not in the document (e.g. in DocumentFragment).
- if ( !markerRange.root.document || markerRange.root.rootName == '$graveyard' ) {
+ // Do not convert if range is in graveyard.
+ if ( markerRange.root.rootName == '$graveyard' ) {
return;
}
@@ -445,8 +445,8 @@ export default class DowncastDispatcher {
* @param {module:engine/view/downcastwriter~DowncastWriter} writer View writer that should be used to modify the view document.
*/
convertMarkerRemove( markerName, markerRange, writer ) {
- // Do not convert if range is in graveyard or not in the document (e.g. in DocumentFragment).
- if ( !markerRange.root.document || markerRange.root.rootName == '$graveyard' ) {
+ // Do not convert if range is in graveyard.
+ if ( markerRange.root.rootName == '$graveyard' ) {
return;
}
diff --git a/packages/ckeditor5-engine/src/conversion/upcasthelpers.js b/packages/ckeditor5-engine/src/conversion/upcasthelpers.js
index 5a828846172..a6ae345b1f6 100644
--- a/packages/ckeditor5-engine/src/conversion/upcasthelpers.js
+++ b/packages/ckeditor5-engine/src/conversion/upcasthelpers.js
@@ -889,8 +889,8 @@ function prepareToAttributeConverter( config, shallow ) {
return;
}
- // Since we are converting to attribute we need an range on which we will set the attribute.
- // If the range is not created yet, we will create it.
+ // Since we are converting to attribute we need a range on which we will set the attribute.
+ // If the range is not created yet, let's create it by converting children of the current node first.
if ( !data.modelRange ) {
// Convert children and set conversion result as a current data.
data = Object.assign( data, conversionApi.convertChildren( data.viewItem, data.modelCursor ) );
@@ -899,6 +899,8 @@ function prepareToAttributeConverter( config, shallow ) {
// Set attribute on current `output`. `Schema` is checked inside this helper function.
const attributeWasSet = setAttributeOn( data.modelRange, { key: modelKey, value: modelValue }, shallow, conversionApi );
+ // It may happen that a converter will try to set an attribute that is not allowed in the given context.
+ // In such a situation we cannot consume the attribute. See: https://github.com/ckeditor/ckeditor5/pull/9249#issuecomment-815658459.
if ( attributeWasSet ) {
conversionApi.consumable.consume( data.viewItem, match.match );
}
@@ -923,6 +925,8 @@ function onlyViewNameIsDefined( viewConfig, viewItem ) {
// Helper function for to-model-attribute converter. Sets model attribute on given range. Checks {@link module:engine/model/schema~Schema}
// to ensure proper model structure.
//
+// If any node on the given range has already defined an attribute with the same name, its value will not be updated.
+//
// @param {module:engine/model/range~Range} modelRange Model range on which attribute should be set.
// @param {Object} modelAttribute Model attribute to set.
// @param {module:engine/conversion/upcastdispatcher~UpcastConversionApi} conversionApi Conversion API.
@@ -934,11 +938,21 @@ function setAttributeOn( modelRange, modelAttribute, shallow, conversionApi ) {
// Set attribute on each item in range according to Schema.
for ( const node of Array.from( modelRange.getItems( { shallow } ) ) ) {
- if ( conversionApi.schema.checkAttribute( node, modelAttribute.key ) ) {
- conversionApi.writer.setAttribute( modelAttribute.key, modelAttribute.value, node );
+ // Skip if not allowed.
+ if ( !conversionApi.schema.checkAttribute( node, modelAttribute.key ) ) {
+ continue;
+ }
- result = true;
+ // Mark the node as consumed even if the attribute will not be updated because it's in a valid context (schema)
+ // and would be converted if the attribute wouldn't be present. See #8921.
+ result = true;
+
+ // Do not override the attribute if it's already present.
+ if ( node.hasAttribute( modelAttribute.key ) ) {
+ continue;
}
+
+ conversionApi.writer.setAttribute( modelAttribute.key, modelAttribute.value, node );
}
return result;
diff --git a/packages/ckeditor5-engine/src/dataprocessor/dataprocessor.jsdoc b/packages/ckeditor5-engine/src/dataprocessor/dataprocessor.jsdoc
index 673ef7d8595..a116ceed5a2 100644
--- a/packages/ckeditor5-engine/src/dataprocessor/dataprocessor.jsdoc
+++ b/packages/ckeditor5-engine/src/dataprocessor/dataprocessor.jsdoc
@@ -49,3 +49,16 @@
* @param {module:engine/view/matcher~MatcherPattern} pattern Pattern matching all view elements whose content should
* be treated as plain text.
*/
+
+/**
+ * If the processor is set to use marked fillers, it will insert nbsp fillers wrapped in spans
+ * (` `), instead of regular nbsp characters (` `).
+ *
+ * This mode allows for more precise handling of block fillers (so they don't leak into editor content) but bloats the editor
+ * data with additional markup.
+ *
+ * This mode may be required by some features and will be turned on by them automatically.
+ *
+ * @method #useFillerType
+ * @param {'default'|'marked'} type Whether to use default or marked nbsp block fillers.
+ */
diff --git a/packages/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js b/packages/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js
index f0d50fde4d5..4b1de28b9f2 100644
--- a/packages/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js
+++ b/packages/ckeditor5-engine/src/dataprocessor/htmldataprocessor.js
@@ -93,6 +93,21 @@ export default class HtmlDataProcessor {
this._domConverter.registerRawContentMatcher( pattern );
}
+ /**
+ * If the processor is set to use marked fillers, it will insert nbsp fillers wrapped in spans
+ * (` `), instead of regular nbsp characters (` `).
+ *
+ * This mode allows for more precise handling of block fillers (so they don't leak into editor content) but bloats the editor
+ * data with additional markup.
+ *
+ * This mode may be required by some features and will be turned on by them automatically.
+ *
+ * @param {'default'|'marked'} type Whether to use default or marked nbsp block fillers.
+ */
+ useFillerType( type ) {
+ this._domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
+ }
+
/**
* Converts an HTML string to its DOM representation. Returns a document fragment containing nodes parsed from
* the provided data.
diff --git a/packages/ckeditor5-engine/src/dataprocessor/xmldataprocessor.js b/packages/ckeditor5-engine/src/dataprocessor/xmldataprocessor.js
index c2162788dc6..141dbef9925 100644
--- a/packages/ckeditor5-engine/src/dataprocessor/xmldataprocessor.js
+++ b/packages/ckeditor5-engine/src/dataprocessor/xmldataprocessor.js
@@ -110,6 +110,21 @@ export default class XmlDataProcessor {
this._domConverter.registerRawContentMatcher( pattern );
}
+ /**
+ * If the processor is set to use marked fillers, it will insert nbsp fillers wrapped in spans
+ * (` `), instead of regular nbsp characters (` `).
+ *
+ * This mode allows for more precise handling of block fillers (so they don't leak into editor content) but bloats the editor
+ * data with additional markup.
+ *
+ * This mode may be required by some features and will be turned on by them automatically.
+ *
+ * @param {'default'|'marked'} type Whether to use default or marked nbsp block fillers.
+ */
+ useFillerType( type ) {
+ this._domConverter.blockFillerMode = type == 'marked' ? 'markedNbsp' : 'nbsp';
+ }
+
/**
* Converts an XML string to its DOM representation. Returns a document fragment containing nodes parsed from
* the provided data.
diff --git a/packages/ckeditor5-engine/src/model/model.js b/packages/ckeditor5-engine/src/model/model.js
index 637273ca404..6ef7b65aa57 100644
--- a/packages/ckeditor5-engine/src/model/model.js
+++ b/packages/ckeditor5-engine/src/model/model.js
@@ -95,21 +95,30 @@ export default class Model {
this.schema.register( '$root', {
isLimit: true
} );
+
this.schema.register( '$block', {
allowIn: '$root',
isBlock: true
} );
+
this.schema.register( '$text', {
allowIn: '$block',
isInline: true,
isContent: true
} );
+
this.schema.register( '$clipboardHolder', {
allowContentOf: '$root',
isLimit: true
} );
this.schema.extend( '$text', { allowIn: '$clipboardHolder' } );
+ this.schema.register( '$documentFragment', {
+ allowContentOf: '$root',
+ isLimit: true
+ } );
+ this.schema.extend( '$text', { allowIn: '$documentFragment' } );
+
// An element needed by the `upcastElementToMarker` converter.
// This element temporarily represents a marker boundary during the conversion process and is removed
// at the end of the conversion. `UpcastDispatcher` or at least `Conversion` class looks like a
diff --git a/packages/ckeditor5-engine/src/model/schema.js b/packages/ckeditor5-engine/src/model/schema.js
index af7db81d7b7..65d38748748 100644
--- a/packages/ckeditor5-engine/src/model/schema.js
+++ b/packages/ckeditor5-engine/src/model/schema.js
@@ -1328,10 +1328,6 @@ export class SchemaContext {
context = context.getAncestors( { includeSelf: true } );
}
- if ( context[ 0 ] && typeof context[ 0 ] != 'string' && context[ 0 ].is( 'documentFragment' ) ) {
- context.shift();
- }
-
this._items = context.map( mapContextItem );
}
@@ -1708,9 +1704,9 @@ function getValues( obj ) {
}
function mapContextItem( ctxItem ) {
- if ( typeof ctxItem == 'string' ) {
+ if ( typeof ctxItem == 'string' || ctxItem.is( 'documentFragment' ) ) {
return {
- name: ctxItem,
+ name: typeof ctxItem == 'string' ? ctxItem : '$documentFragment',
* getAttributeKeys() {},
diff --git a/packages/ckeditor5-engine/src/view/domconverter.js b/packages/ckeditor5-engine/src/view/domconverter.js
index e263bb9d41a..5e4e3b86b99 100644
--- a/packages/ckeditor5-engine/src/view/domconverter.js
+++ b/packages/ckeditor5-engine/src/view/domconverter.js
@@ -17,7 +17,10 @@ import ViewSelection from './selection';
import ViewDocumentFragment from './documentfragment';
import ViewTreeWalker from './treewalker';
import Matcher from './matcher';
-import { BR_FILLER, getDataWithoutFiller, INLINE_FILLER_LENGTH, isInlineFiller, NBSP_FILLER, startsWithFiller } from './filler';
+import {
+ BR_FILLER, INLINE_FILLER_LENGTH, NBSP_FILLER, MARKED_NBSP_FILLER,
+ getDataWithoutFiller, isInlineFiller, startsWithFiller
+} from './filler';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import indexOf from '@ckeditor/ckeditor5-utils/src/dom/indexof';
@@ -26,8 +29,9 @@ import getCommonAncestor from '@ckeditor/ckeditor5-utils/src/dom/getcommonancest
import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
import { isElement } from 'lodash-es';
-// eslint-disable-next-line new-cap
-const BR_FILLER_REF = BR_FILLER( document );
+const BR_FILLER_REF = BR_FILLER( document ); // eslint-disable-line new-cap
+const NBSP_FILLER_REF = NBSP_FILLER( document ); // eslint-disable-line new-cap
+const MARKED_NBSP_FILLER_REF = MARKED_NBSP_FILLER( document ); // eslint-disable-line new-cap
/**
* `DomConverter` is a set of tools to do transformations between DOM nodes and view nodes. It also handles
@@ -60,8 +64,7 @@ export default class DomConverter {
/**
* The mode of a block filler used by the DOM converter.
*
- * @readonly
- * @member {'br'|'nbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
+ * @member {'br'|'nbsp'|'markedNbsp'} module:engine/view/domconverter~DomConverter#blockFillerMode
*/
this.blockFillerMode = options.blockFillerMode || 'br';
@@ -86,16 +89,6 @@ export default class DomConverter {
*/
this.blockElements = [ 'p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'dd', 'dt', 'figcaption', 'td', 'th' ];
- /**
- * Block {@link module:engine/view/filler filler} creator, which is used to create all block fillers during the
- * view-to-DOM conversion and to recognize block fillers during the DOM-to-view conversion.
- *
- * @readonly
- * @private
- * @member {Function} module:engine/view/domconverter~DomConverter#_blockFiller
- */
- this._blockFiller = this.blockFillerMode == 'br' ? BR_FILLER : NBSP_FILLER;
-
/**
* The DOM-to-view mapping.
*
@@ -297,7 +290,7 @@ export default class DomConverter {
for ( const childView of viewElement.getChildren() ) {
if ( fillerPositionOffset === offset ) {
- yield this._blockFiller( domDocument );
+ yield this._getBlockFiller( domDocument );
}
yield this.viewToDom( childView, domDocument, options );
@@ -306,7 +299,7 @@ export default class DomConverter {
}
if ( fillerPositionOffset === offset ) {
- yield this._blockFiller( domDocument );
+ yield this._getBlockFiller( domDocument );
}
}
@@ -413,7 +406,7 @@ export default class DomConverter {
* or `null` if DOM node is a {@link module:engine/view/filler filler} or the given node is an empty text node.
*/
domToView( domNode, options = {} ) {
- if ( this.isBlockFiller( domNode, this.blockFillerMode ) ) {
+ if ( this.isBlockFiller( domNode ) ) {
return null;
}
@@ -581,7 +574,7 @@ export default class DomConverter {
* @returns {module:engine/view/position~Position} viewPosition View position.
*/
domPositionToView( domParent, domOffset ) {
- if ( this.isBlockFiller( domParent, this.blockFillerMode ) ) {
+ if ( this.isBlockFiller( domParent ) ) {
return this.domPositionToView( domParent.parentNode, indexOf( domParent ) );
}
@@ -863,13 +856,13 @@ export default class DomConverter {
return domNode.isEqualNode( BR_FILLER_REF );
}
- // Special case for
in which case the
should be treated as filler even
- // when we're in the 'nbsp' mode. See ckeditor5#5564.
+ // Special case for
in which
should be treated as filler even when we are not in the 'br' mode. See ckeditor5#5564.
if ( domNode.tagName === 'BR' && hasBlockParent( domNode, this.blockElements ) && domNode.parentNode.childNodes.length === 1 ) {
return true;
}
- return isNbspBlockFiller( domNode, this.blockElements );
+ // If not in 'br' mode, try recognizing both marked and regular nbsp block fillers.
+ return domNode.isEqualNode( MARKED_NBSP_FILLER_REF ) || isNbspBlockFiller( domNode, this.blockElements );
}
/**
@@ -956,6 +949,24 @@ export default class DomConverter {
this._rawContentElementMatcher.add( pattern );
}
+ /**
+ * Returns block {@link module:engine/view/filler filler} node based on current {@link #blockFillerMode} setting.
+ *
+ * @private
+ * @params {Document} domDocument
+ * @returns {Node} filler
+ */
+ _getBlockFiller( domDocument ) {
+ switch ( this.blockFillerMode ) {
+ case 'nbsp':
+ return NBSP_FILLER( domDocument ); // eslint-disable-line new-cap
+ case 'markedNbsp':
+ return MARKED_NBSP_FILLER( domDocument ); // eslint-disable-line new-cap
+ case 'br':
+ return BR_FILLER( domDocument ); // eslint-disable-line new-cap
+ }
+ }
+
/**
* Checks if the given DOM position is a correct place for selection boundary. See {@link #isDomSelectionCorrect}.
*
@@ -1320,7 +1331,7 @@ function forEachDomNodeAncestor( node, callback ) {
// @param {Node} domNode DOM node.
// @returns {Boolean}
function isNbspBlockFiller( domNode, blockElements ) {
- const isNBSP = isText( domNode ) && domNode.data == '\u00A0';
+ const isNBSP = domNode.isEqualNode( NBSP_FILLER_REF );
return isNBSP && hasBlockParent( domNode, blockElements ) && domNode.parentNode.childNodes.length === 1;
}
@@ -1340,8 +1351,9 @@ function hasBlockParent( domNode, blockElements ) {
*
* Possible values:
*
- * * `br` - for `
` block filler used in editing view,
- * * `nbsp` - for ` ` block fillers used in the data.
+ * * `br` - for `
` block filler used in the editing view,
+ * * `nbsp` - for ` ` block fillers used in the data,
+ * * `markedNbsp` - for nbsp block fillers wrapped in a span: ` ` used in the data.
*
* @typedef {String} module:engine/view/filler~BlockFillerMode
*/
diff --git a/packages/ckeditor5-engine/src/view/filler.js b/packages/ckeditor5-engine/src/view/filler.js
index 1d73295568b..39f17e58d1a 100644
--- a/packages/ckeditor5-engine/src/view/filler.js
+++ b/packages/ckeditor5-engine/src/view/filler.js
@@ -18,7 +18,8 @@ import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
* it is transparent for the selection, so when the caret is before the `
` and user presses right arrow he will be
* moved to the next paragraph, not after the `
`. The disadvantage is that it breaks a block, so it can not be used
* in the middle of a line of text. The {@link module:engine/view/filler~BR_FILLER `
` filler} can be replaced with any other
- * character in the data output, for instance {@link module:engine/view/filler~NBSP_FILLER non-breaking space}.
+ * character in the data output, for instance {@link module:engine/view/filler~NBSP_FILLER non-breaking space} or
+ * {@link module:engine/view/filler~MARKED_NBSP_FILLER marked non-breaking space}.
*
* * Inline filler is a filler which does not break a line of text, so it can be used inside the text, for instance in the empty
* `` surrendered by text: `foobar`, if we want to put the caret there. CKEditor uses a sequence of the zero-width
@@ -38,16 +39,34 @@ import isText from '@ckeditor/ckeditor5-utils/src/dom/istext';
* Non-breaking space filler creator. This is a function which creates ` ` text node.
* It defines how the filler is created.
*
+ * @see module:engine/view/filler~MARKED_NBSP_FILLER
* @see module:engine/view/filler~BR_FILLER
* @function
*/
export const NBSP_FILLER = domDocument => domDocument.createTextNode( '\u00A0' );
+/**
+ * Marked non-breaking space filler creator. This is a function which creates ` ` element.
+ * It defines how the filler is created.
+ *
+ * @see module:engine/view/filler~NBSP_FILLER
+ * @see module:engine/view/filler~BR_FILLER
+ * @function
+ */
+export const MARKED_NBSP_FILLER = domDocument => {
+ const span = domDocument.createElement( 'span' );
+ span.dataset.ckeFiller = true;
+ span.innerHTML = '\u00A0';
+
+ return span;
+};
+
/**
* `
` filler creator. This is a function which creates `
` element.
* It defines how the filler is created.
*
* @see module:engine/view/filler~NBSP_FILLER
+ * @see module:engine/view/filler~MARKED_NBSP_FILLER
* @function
*/
export const BR_FILLER = domDocument => {
diff --git a/packages/ckeditor5-engine/tests/controller/datacontroller.js b/packages/ckeditor5-engine/tests/controller/datacontroller.js
index 9ba1ccea34e..f39001861ce 100644
--- a/packages/ckeditor5-engine/tests/controller/datacontroller.js
+++ b/packages/ckeditor5-engine/tests/controller/datacontroller.js
@@ -654,18 +654,24 @@ describe( 'DataController', () => {
expect( stringifyView( viewDocumentFragment ) ).to.equal( 'foo' );
} );
- it( 'should convert a document fragment', () => {
+ it( 'should convert a document fragment and its markers', () => {
+ downcastHelpers.markerToData( { model: 'foo' } );
const modelDocumentFragment = parseModel( 'foo bar ', schema );
+
+ const range = model.createRange(
+ model.createPositionAt( modelDocumentFragment.getChild( 0 ), 1 ),
+ model.createPositionAt( modelDocumentFragment.getChild( 1 ), 2 )
+ );
+ modelDocumentFragment.markers.set( 'foo:bar', range );
+
const viewDocumentFragment = data.toView( modelDocumentFragment );
expect( viewDocumentFragment ).to.be.instanceOf( ViewDocumentFragment );
expect( viewDocumentFragment ).to.have.property( 'childCount', 2 );
- const viewElement = viewDocumentFragment.getChild( 0 );
-
- expect( viewElement.name ).to.equal( 'p' );
- expect( viewElement.childCount ).to.equal( 1 );
- expect( viewElement.getChild( 0 ).data ).to.equal( 'foo' );
+ expect( stringifyView( viewDocumentFragment ) ).to.equal(
+ 'f oo
ba r
'
+ );
} );
it( 'should keep view-model mapping', () => {
diff --git a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js
index 54cf8f04b93..3bf2e51f0ea 100644
--- a/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js
+++ b/packages/ckeditor5-engine/tests/conversion/downcastdispatcher.js
@@ -9,6 +9,7 @@ import Mapper from '../../src/conversion/mapper';
import Model from '../../src/model/model';
import ModelText from '../../src/model/text';
import ModelElement from '../../src/model/element';
+import ModelDocumentFragment from '../../src/model/documentfragment';
import ModelRange from '../../src/model/range';
import View from '../../src/view/view';
@@ -500,21 +501,22 @@ describe( 'DowncastDispatcher', () => {
expect( spy.calledOnce ).to.be.true;
} );
- it( 'should not convert marker if it is in graveyard', () => {
- const gyRange = model.createRange( model.createPositionAt( doc.graveyard, 0 ), model.createPositionAt( doc.graveyard, 0 ) );
+ it( 'should convert marker in document fragment', () => {
+ const text = new ModelText( 'foo' );
+ const docFrag = new ModelDocumentFragment( text );
+ const eleRange = model.createRange( model.createPositionAt( docFrag, 1 ), model.createPositionAt( docFrag, 2 ) );
sinon.spy( dispatcher, 'fire' );
- dispatcher.convertMarkerAdd( 'name', gyRange );
+ dispatcher.convertMarkerAdd( 'name', eleRange );
- expect( dispatcher.fire.called ).to.be.false;
+ expect( dispatcher.fire.called ).to.be.true;
} );
- it( 'should not convert marker if it is not in model root', () => {
- const element = new ModelElement( 'element', null, new ModelText( 'foo' ) );
- const eleRange = model.createRange( model.createPositionAt( element, 1 ), model.createPositionAt( element, 2 ) );
+ it( 'should not convert marker if it is in graveyard', () => {
+ const gyRange = model.createRange( model.createPositionAt( doc.graveyard, 0 ), model.createPositionAt( doc.graveyard, 0 ) );
sinon.spy( dispatcher, 'fire' );
- dispatcher.convertMarkerAdd( 'name', eleRange );
+ dispatcher.convertMarkerAdd( 'name', gyRange );
expect( dispatcher.fire.called ).to.be.false;
} );
@@ -662,14 +664,15 @@ describe( 'DowncastDispatcher', () => {
expect( dispatcher.fire.called ).to.be.false;
} );
- it( 'should not convert marker if it is not in model root', () => {
- const element = new ModelElement( 'element', null, new ModelText( 'foo' ) );
- const eleRange = model.createRange( model.createPositionAt( element, 1 ), model.createPositionAt( element, 2 ) );
+ it( 'should convert marker in document fragment', () => {
+ const text = new ModelText( 'foo' );
+ const docFrag = new ModelDocumentFragment( text );
+ const eleRange = model.createRange( model.createPositionAt( docFrag, 1 ), model.createPositionAt( docFrag, 2 ) );
sinon.spy( dispatcher, 'fire' );
dispatcher.convertMarkerRemove( 'name', eleRange );
- expect( dispatcher.fire.called ).to.be.false;
+ expect( dispatcher.fire.called ).to.be.true;
} );
it( 'should fire conversion for the range', () => {
diff --git a/packages/ckeditor5-engine/tests/conversion/upcasthelpers.js b/packages/ckeditor5-engine/tests/conversion/upcasthelpers.js
index 5f1d12baf04..37ecbe6bf15 100644
--- a/packages/ckeditor5-engine/tests/conversion/upcasthelpers.js
+++ b/packages/ckeditor5-engine/tests/conversion/upcasthelpers.js
@@ -376,6 +376,125 @@ describe( 'UpcastHelpers', () => {
'<$text bold="true">Foo$text> '
);
} );
+
+ // #8921.
+ describe( 'overwriting attributes while converting nested elements', () => {
+ beforeEach( () => {
+ schema.extend( '$text', {
+ allowAttributes: [ 'fontSize', 'fontColor' ]
+ } );
+
+ upcastHelpers.elementToAttribute( {
+ view: {
+ name: 'span',
+ styles: {
+ 'font-size': /[\s\S]+/
+ }
+ },
+ model: {
+ key: 'fontSize',
+ value: viewElement => {
+ const fontSize = viewElement.getStyle( 'font-size' );
+ const value = fontSize.substr( 0, fontSize.length - 2 );
+
+ return Number( value );
+ }
+ }
+ } );
+
+ upcastHelpers.elementToAttribute( {
+ view: {
+ name: 'span',
+ styles: {
+ 'color': /#[a-f0-9]{6}/
+ }
+ },
+ model: {
+ key: 'fontColor',
+ value: viewElement => viewElement.getStyle( 'color' )
+ }
+ } );
+ } );
+
+ it( 'should not overwrite attributes if nested elements have the same attribute but different values', () => {
+ const viewElement = viewParse( 'Bar' );
+
+ expectResult(
+ viewElement,
+ '<$text fontSize="11">Bar$text>'
+ );
+ } );
+
+ it( 'should convert text before the nested duplicated attribute with the most outer value', () => {
+ const viewElement = viewParse( 'FooBar' );
+
+ expectResult(
+ viewElement,
+ '<$text fontSize="9">Foo$text><$text fontSize="11">Bar$text>'
+ );
+ } );
+
+ it( 'should convert text after the nested duplicated attribute with the most outer values', () => {
+ const viewElement = viewParse( 'BarBom' );
+
+ expectResult(
+ viewElement,
+ '<$text fontSize="11">Bar$text><$text fontSize="9">Bom$text>'
+ );
+ } );
+
+ it( 'should convert texts before and after the nested duplicated attribute with the most outer value', () => {
+ const viewElement = viewParse( 'FooBarBom' );
+
+ expectResult(
+ viewElement,
+ '<$text fontSize="9">Foo$text><$text fontSize="11">Bar$text><$text fontSize="9">Bom$text>'
+ );
+ } );
+
+ it( 'should work with multiple duplicated attributes', () => {
+ const viewElement = viewParse(
+ 'Bar'
+ );
+
+ expectResult(
+ viewElement,
+ '<$text fontColor="#ff0000" fontSize="11">Bar$text>'
+ );
+ } );
+
+ it( 'should convert non-duplicated attributes from the most outer element', () => {
+ const viewElement = viewParse(
+ 'Bar'
+ );
+
+ expectResult(
+ viewElement,
+ '<$text fontColor="#0000ff" fontSize="11">Bar$text>'
+ );
+ } );
+
+ // See https://github.com/ckeditor/ckeditor5/pull/9249#issuecomment-813935851
+ it( 'should consume both elements even if the attribute from the most inner element will be used', () => {
+ upcastDispatcher.on( 'element:span', ( evt, data, conversionApi ) => {
+ const viewItem = data.viewItem;
+ const wasConsumed = conversionApi.consumable.consume( viewItem, {
+ styles: [ 'font-size' ]
+ } );
+
+ expect( wasConsumed, `span[fontSize=${ viewItem.getStyle( 'font-size' ) }]` ).to.equal( false );
+ }, { priority: 'lowest' } );
+
+ const viewElement = viewParse(
+ 'Bar'
+ );
+
+ expectResult(
+ viewElement,
+ '<$text fontSize="11">Bar$text>'
+ );
+ } );
+ } );
} );
describe( 'attributeToAttribute()', () => {
diff --git a/packages/ckeditor5-engine/tests/dataprocessor/htmldataprocessor.js b/packages/ckeditor5-engine/tests/dataprocessor/htmldataprocessor.js
index 5b2785fb935..9df53d24a0d 100644
--- a/packages/ckeditor5-engine/tests/dataprocessor/htmldataprocessor.js
+++ b/packages/ckeditor5-engine/tests/dataprocessor/htmldataprocessor.js
@@ -133,4 +133,20 @@ describe( 'HtmlDataProcessor', () => {
expect( fragment.getChild( 1 ).getCustomProperty( '$rawContent' ) ).to.equal( ' abc ' );
} );
} );
+
+ describe( 'useFillerType()', () => {
+ it( 'should turn on and off using marked block fillers', () => {
+ const fragment = parse( ' ' );
+
+ expect( dataProcessor.toData( fragment ) ).to.equal( '
' );
+
+ dataProcessor.useFillerType( 'marked' );
+
+ expect( dataProcessor.toData( fragment ) ).to.equal( '
' );
+
+ dataProcessor.useFillerType( 'default' );
+
+ expect( dataProcessor.toData( fragment ) ).to.equal( '
' );
+ } );
+ } );
} );
diff --git a/packages/ckeditor5-engine/tests/dataprocessor/xmldataprocessor.js b/packages/ckeditor5-engine/tests/dataprocessor/xmldataprocessor.js
index eeb043b6510..824b74cec79 100644
--- a/packages/ckeditor5-engine/tests/dataprocessor/xmldataprocessor.js
+++ b/packages/ckeditor5-engine/tests/dataprocessor/xmldataprocessor.js
@@ -122,4 +122,20 @@ describe( 'XmlDataProcessor', () => {
expect( fragment.getChild( 1 ).getCustomProperty( '$rawContent' ) ).to.equal( ' abc ' );
} );
} );
+
+ describe( 'useFillerType()', () => {
+ it( 'should turn on and off using marked block fillers', () => {
+ const fragment = parse( ' ' );
+
+ expect( dataProcessor.toData( fragment ) ).to.equal( '
' );
+
+ dataProcessor.useFillerType( 'marked' );
+
+ expect( dataProcessor.toData( fragment ) ).to.equal( '
' );
+
+ dataProcessor.useFillerType( 'default' );
+
+ expect( dataProcessor.toData( fragment ) ).to.equal( '
' );
+ } );
+ } );
} );
diff --git a/packages/ckeditor5-engine/tests/model/model.js b/packages/ckeditor5-engine/tests/model/model.js
index d2d8ce3fe71..953407cb128 100644
--- a/packages/ckeditor5-engine/tests/model/model.js
+++ b/packages/ckeditor5-engine/tests/model/model.js
@@ -54,6 +54,13 @@ describe( 'Model', () => {
expect( schema.checkChild( [ '$clipboardHolder' ], '$block' ) ).to.be.true;
} );
+ it( 'registers $documentFragment to the schema', () => {
+ expect( schema.isRegistered( '$documentFragment' ) ).to.be.true;
+ expect( schema.isLimit( '$documentFragment' ) ).to.be.true;
+ expect( schema.checkChild( [ '$documentFragment' ], '$text' ) ).to.be.true;
+ expect( schema.checkChild( [ '$documentFragment' ], '$block' ) ).to.be.true;
+ } );
+
it( 'registers $marker to the schema', () => {
model.document.createRoot( '$anywhere', 'anywhere' );
schema.register( 'anything' );
diff --git a/packages/ckeditor5-engine/tests/model/operation/transform/unwrap.js b/packages/ckeditor5-engine/tests/model/operation/transform/unwrap.js
index f994115f07d..40a1828c680 100644
--- a/packages/ckeditor5-engine/tests/model/operation/transform/unwrap.js
+++ b/packages/ckeditor5-engine/tests/model/operation/transform/unwrap.js
@@ -335,7 +335,9 @@ describe( 'transform', () => {
expectClients(
'' +
'A ' +
- 'B ' +
+ '' +
+ 'B ' +
+ '
' +
'C ' +
'
'
);
diff --git a/packages/ckeditor5-engine/tests/model/schema.js b/packages/ckeditor5-engine/tests/model/schema.js
index 0baa547c7d1..677ac52f1c1 100644
--- a/packages/ckeditor5-engine/tests/model/schema.js
+++ b/packages/ckeditor5-engine/tests/model/schema.js
@@ -3233,33 +3233,32 @@ describe( 'SchemaContext', () => {
expect( ctx ).to.equal( previousCtx );
} );
- it( 'filters out DocumentFragment when it is a first item of context - array', () => {
+ it( 'creates context in DocumentFragment - array with string', () => {
const ctx = new SchemaContext( [ new DocumentFragment(), 'paragraph' ] );
- expect( ctx.length ).to.equal( 1 );
- expect( Array.from( ctx.getNames() ) ).to.deep.equal( [ 'paragraph' ] );
+ expect( ctx.length ).to.equal( 2 );
+ expect( Array.from( ctx.getNames() ) ).to.deep.equal( [ '$documentFragment', 'paragraph' ] );
} );
- it( 'filters out DocumentFragment when it is a first item of context - element', () => {
+ it( 'creates context in DocumentFragment - element', () => {
const p = new Element( 'paragraph' );
const docFrag = new DocumentFragment();
docFrag._appendChild( p );
const ctx = new SchemaContext( p );
- expect( ctx.length ).to.equal( 1 );
- expect( Array.from( ctx.getNames() ) ).to.deep.equal( [ 'paragraph' ] );
+ expect( ctx.length ).to.equal( 2 );
+ expect( Array.from( ctx.getNames() ) ).to.deep.equal( [ '$documentFragment', 'paragraph' ] );
} );
- it( 'filters out DocumentFragment when it is a first item of context - position', () => {
+ it( 'creates context in DocumentFragment - position', () => {
const p = new Element( 'paragraph' );
- const docFrag = new DocumentFragment();
- docFrag._appendChild( p );
-
- const ctx = new SchemaContext( new Position( docFrag, [ 0, 0 ] ) );
+ const docFrag = new DocumentFragment( p );
+ const pos = Position._createAt( docFrag.getChild( 0 ), 0 );
+ const ctx = new SchemaContext( pos );
- expect( ctx.length ).to.equal( 1 );
- expect( Array.from( ctx.getNames() ) ).to.deep.equal( [ 'paragraph' ] );
+ expect( ctx.length ).to.equal( 2 );
+ expect( Array.from( ctx.getNames() ) ).to.deep.equal( [ '$documentFragment', 'paragraph' ] );
} );
} );
diff --git a/packages/ckeditor5-engine/tests/view/domconverter/domconverter.js b/packages/ckeditor5-engine/tests/view/domconverter/domconverter.js
index 7806c1948ae..c30f28cc18f 100644
--- a/packages/ckeditor5-engine/tests/view/domconverter/domconverter.js
+++ b/packages/ckeditor5-engine/tests/view/domconverter/domconverter.js
@@ -10,7 +10,7 @@ import ViewEditable from '../../../src/view/editableelement';
import ViewDocument from '../../../src/view/document';
import ViewUIElement from '../../../src/view/uielement';
import ViewContainerElement from '../../../src/view/containerelement';
-import { BR_FILLER, INLINE_FILLER, INLINE_FILLER_LENGTH, NBSP_FILLER } from '../../../src/view/filler';
+import { BR_FILLER, INLINE_FILLER, INLINE_FILLER_LENGTH, NBSP_FILLER, MARKED_NBSP_FILLER } from '../../../src/view/filler';
import testUtils from '@ckeditor/ckeditor5-core/tests/_utils/utils';
import global from '@ckeditor/ckeditor5-utils/src/dom/global';
import { StylesProcessor } from '../../../src/view/stylesmap';
@@ -295,88 +295,96 @@ describe( 'DomConverter', () => {
} );
describe( 'isBlockFiller()', () => {
- describe( 'mode "nbsp"', () => {
- beforeEach( () => {
- converter = new DomConverter( viewDocument, { blockFillerMode: 'nbsp' } );
- } );
+ for ( const mode of [ 'nbsp', 'markedNbsp' ] ) {
+ describe( 'mode "' + mode + '"', () => {
+ beforeEach( () => {
+ converter = new DomConverter( viewDocument, { blockFillerMode: mode } );
+ } );
- it( 'should return true if the node is an nbsp filler and is a single child of a block level element', () => {
- const nbspFillerInstance = NBSP_FILLER( document ); // eslint-disable-line new-cap
+ it( 'should return true if the node is an nbsp filler and is a single child of a block level element', () => {
+ const nbspFillerInstance = NBSP_FILLER( document ); // eslint-disable-line new-cap
- const context = document.createElement( 'div' );
- context.appendChild( nbspFillerInstance );
+ const context = document.createElement( 'div' );
+ context.appendChild( nbspFillerInstance );
- expect( converter.isBlockFiller( nbspFillerInstance ) ).to.be.true;
- } );
+ expect( converter.isBlockFiller( nbspFillerInstance ) ).to.be.true;
+ } );
- it( 'should return false if the node is an nbsp filler and is not a single child of a block level element', () => {
- const nbspFillerInstance = NBSP_FILLER( document ); // eslint-disable-line new-cap
+ it( 'should return false if the node is an nbsp filler and is not a single child of a block level element', () => {
+ const nbspFillerInstance = NBSP_FILLER( document ); // eslint-disable-line new-cap
- const context = document.createElement( 'div' );
- context.appendChild( nbspFillerInstance );
- context.appendChild( document.createTextNode( 'a' ) );
+ const context = document.createElement( 'div' );
+ context.appendChild( nbspFillerInstance );
+ context.appendChild( document.createTextNode( 'a' ) );
- expect( converter.isBlockFiller( nbspFillerInstance ) ).to.be.false;
- } );
+ expect( converter.isBlockFiller( nbspFillerInstance ) ).to.be.false;
+ } );
- it( 'should return false if there are two nbsp fillers in a block element', () => {
- const nbspFillerInstance = NBSP_FILLER( document ); // eslint-disable-line new-cap
+ it( 'should return false if there are two nbsp fillers in a block element', () => {
+ const nbspFillerInstance = NBSP_FILLER( document ); // eslint-disable-line new-cap
- const context = document.createElement( 'div' );
- context.appendChild( nbspFillerInstance );
- context.appendChild( NBSP_FILLER( document ) ); // eslint-disable-line new-cap
+ const context = document.createElement( 'div' );
+ context.appendChild( nbspFillerInstance );
+ context.appendChild( NBSP_FILLER( document ) ); // eslint-disable-line new-cap
- expect( converter.isBlockFiller( nbspFillerInstance ) ).to.be.false;
- } );
+ expect( converter.isBlockFiller( nbspFillerInstance ) ).to.be.false;
+ } );
- it( 'should return false filler is placed in a non-block element', () => {
- const nbspFillerInstance = NBSP_FILLER( document ); // eslint-disable-line new-cap
+ it( 'should return false filler is placed in a non-block element', () => {
+ const nbspFillerInstance = NBSP_FILLER( document ); // eslint-disable-line new-cap
- const context = document.createElement( 'span' );
- context.appendChild( nbspFillerInstance );
+ const context = document.createElement( 'span' );
+ context.appendChild( nbspFillerInstance );
- expect( converter.isBlockFiller( nbspFillerInstance ) ).to.be.false;
- } );
+ expect( converter.isBlockFiller( nbspFillerInstance ) ).to.be.false;
+ } );
- it( 'should return false if the node is an instance of the BR block filler', () => {
- const brFillerInstance = BR_FILLER( document ); // eslint-disable-line new-cap
+ it( 'should return false if the node is an instance of the BR block filler', () => {
+ const brFillerInstance = BR_FILLER( document ); // eslint-disable-line new-cap
- expect( converter.isBlockFiller( brFillerInstance ) ).to.be.false;
- } );
+ expect( converter.isBlockFiller( brFillerInstance ) ).to.be.false;
+ } );
- it( 'should return false for inline filler', () => {
- expect( converter.isBlockFiller( document.createTextNode( INLINE_FILLER ) ) ).to.be.false;
- } );
+ it( 'should return false for inline filler', () => {
+ expect( converter.isBlockFiller( document.createTextNode( INLINE_FILLER ) ) ).to.be.false;
+ } );
- it( 'should return false for a normal
element', () => {
- const context = document.createElement( 'div' );
- context.innerHTML = 'x
x';
+ it( 'should return false for a normal
element', () => {
+ const context = document.createElement( 'div' );
+ context.innerHTML = 'x
x';
- expect( converter.isBlockFiller( context.childNodes[ 1 ] ) ).to.be.false;
- } );
+ expect( converter.isBlockFiller( context.childNodes[ 1 ] ) ).to.be.false;
+ } );
- // SPECIAL CASE (see ckeditor5#5564).
- it( 'should return true for a
element which is the only child of its block parent', () => {
- const context = document.createElement( 'div' );
- context.innerHTML = '
';
+ // SPECIAL CASE (see ckeditor5#5564).
+ it( 'should return true for a
element which is the only child of its block parent', () => {
+ const context = document.createElement( 'div' );
+ context.innerHTML = '
';
- expect( converter.isBlockFiller( context.firstChild ) ).to.be.true;
- } );
+ expect( converter.isBlockFiller( context.firstChild ) ).to.be.true;
+ } );
- it( 'should return false for a
element which is the only child of its non-block parent', () => {
- const context = document.createElement( 'span' );
- context.innerHTML = '
';
+ it( 'should return false for a
element which is the only child of its non-block parent', () => {
+ const context = document.createElement( 'span' );
+ context.innerHTML = '
';
- expect( converter.isBlockFiller( context.firstChild ) ).to.be.false;
- } );
+ expect( converter.isBlockFiller( context.firstChild ) ).to.be.false;
+ } );
- it( 'should return false for a
element which is followed by an nbsp', () => {
- const context = document.createElement( 'span' );
- context.innerHTML = '
';
+ it( 'should return false for a
element which is followed by an nbsp', () => {
+ const context = document.createElement( 'span' );
+ context.innerHTML = '
';
- expect( converter.isBlockFiller( context.firstChild ) ).to.be.false;
+ expect( converter.isBlockFiller( context.firstChild ) ).to.be.false;
+ } );
+
+ it( 'should return true if the node is an instance of the marked nbsp block filler', () => {
+ const markedNbspFillerInstance = MARKED_NBSP_FILLER( document ); // eslint-disable-line new-cap
+
+ expect( converter.isBlockFiller( markedNbspFillerInstance ) ).to.be.true;
+ } );
} );
- } );
+ }
describe( 'mode "br"', () => {
beforeEach( () => {
diff --git a/packages/ckeditor5-engine/tests/view/domconverter/view-to-dom.js b/packages/ckeditor5-engine/tests/view/domconverter/view-to-dom.js
index 9fd50253229..0f701eae1f8 100644
--- a/packages/ckeditor5-engine/tests/view/domconverter/view-to-dom.js
+++ b/packages/ckeditor5-engine/tests/view/domconverter/view-to-dom.js
@@ -14,7 +14,7 @@ import ViewEmptyElement from '../../../src/view/emptyelement';
import DomConverter from '../../../src/view/domconverter';
import ViewDocumentFragment from '../../../src/view/documentfragment';
import ViewDocument from '../../../src/view/document';
-import { INLINE_FILLER, INLINE_FILLER_LENGTH } from '../../../src/view/filler';
+import { INLINE_FILLER, INLINE_FILLER_LENGTH, BR_FILLER, NBSP_FILLER, MARKED_NBSP_FILLER } from '../../../src/view/filler';
import { parse } from '../../../src/dev-utils/view';
@@ -663,6 +663,39 @@ describe( 'DomConverter', () => {
expect( domChildren[ 1 ].data ).to.equal( 'foo' );
} );
+ it( 'should add proper filler type - br', () => {
+ converter.blockFillerMode = 'br';
+
+ const viewP = parse( ' ' );
+
+ const domChildren = Array.from( converter.viewChildrenToDom( viewP, document ) );
+ const filler = domChildren[ 0 ];
+
+ expect( filler.isEqualNode( BR_FILLER( document ) ) ).to.be.true; // eslint-disable-line new-cap
+ } );
+
+ it( 'should add proper filler type - nbsp', () => {
+ converter.blockFillerMode = 'nbsp';
+
+ const viewP = parse( ' ' );
+
+ const domChildren = Array.from( converter.viewChildrenToDom( viewP, document ) );
+ const filler = domChildren[ 0 ];
+
+ expect( filler.isEqualNode( NBSP_FILLER( document ) ) ).to.be.true; // eslint-disable-line new-cap
+ } );
+
+ it( 'should add proper filler type - markedNbsp', () => {
+ converter.blockFillerMode = 'markedNbsp';
+
+ const viewP = parse( ' ' );
+
+ const domChildren = Array.from( converter.viewChildrenToDom( viewP, document ) );
+ const filler = domChildren[ 0 ];
+
+ expect( filler.isEqualNode( MARKED_NBSP_FILLER( document ) ) ).to.be.true; // eslint-disable-line new-cap
+ } );
+
it( 'should pass options', () => {
const viewP = parse( 'foobar ' );
diff --git a/packages/ckeditor5-font/docs/features/font.md b/packages/ckeditor5-font/docs/features/font.md
index e09bc5a46e2..5acf2915752 100644
--- a/packages/ckeditor5-font/docs/features/font.md
+++ b/packages/ckeditor5-font/docs/features/font.md
@@ -22,7 +22,7 @@ The {@link module:font/font~Font} plugin provides extended text formatting optio
## Related features
Here are some more CKEditor 5 features that can help you format your content:
-* {@link features/basic-styles Basic font styles} – The essentials, like **bold**, *italic* and others.
+* {@link features/basic-styles Basic text styles} – The essentials, like **bold**, *italic* and others.
* {@link features/text-alignment Text alignment} – Because it does matter whether the content is left, right, centered or justified.
* {@link features/headings Headings} – Divide your content into sections.
* {@link features/highlight Highlight} – Mark important words and passages, aiding a review or drawing attention to specific parts of content.
diff --git a/packages/ckeditor5-font/lang/translations/de-ch.po b/packages/ckeditor5-font/lang/translations/de-ch.po
new file mode 100644
index 00000000000..a2578b572d8
--- /dev/null
+++ b/packages/ckeditor5-font/lang/translations/de-ch.po
@@ -0,0 +1,57 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: German (Switzerland) (https://www.transifex.com/ckeditor/teams/11143/de_CH/)\n"
+"Language: de_CH\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Tooltip for the font size dropdown."
+msgid "Font Size"
+msgstr "Schriftgrösse"
+
+msgctxt "Dropdown option label for the 'tiny' font size preset."
+msgid "Tiny"
+msgstr "Winzig"
+
+msgctxt "Dropdown option label for the 'small' font size preset."
+msgid "Small"
+msgstr "Klein"
+
+msgctxt "Dropdown option label for the 'big' font size preset."
+msgid "Big"
+msgstr "Gross"
+
+msgctxt "Dropdown option label for the 'huge' font size preset."
+msgid "Huge"
+msgstr "Riesig"
+
+msgctxt "Tooltip for the font family dropdown."
+msgid "Font Family"
+msgstr "Schriftfamilie"
+
+msgctxt "Dropdown option label for the default font family."
+msgid "Default"
+msgstr "Standard"
+
+msgctxt "Label of a button that allows selecting a font color."
+msgid "Font Color"
+msgstr "Schriftfarbe"
+
+msgctxt "Label of a button that allows selecting a font background color."
+msgid "Font Background Color"
+msgstr "Hintergrundfarbe der Schrift"
+
+msgctxt "Title of a color picker section containing the colors currently used in the document."
+msgid "Document colors"
+msgstr "Farben des Dokuments"
diff --git a/packages/ckeditor5-font/tests/fontcolor/fontcolorediting.js b/packages/ckeditor5-font/tests/fontcolor/fontcolorediting.js
index 3ce37df8c90..c12fa27f3b4 100644
--- a/packages/ckeditor5-font/tests/fontcolor/fontcolorediting.js
+++ b/packages/ckeditor5-font/tests/fontcolor/fontcolorediting.js
@@ -343,5 +343,60 @@ describe( 'FontColorEditing', () => {
'foobaro
'
);
} );
+
+ // #8921
+ describe( 'nested elements that will be converted into the fontColor attribute', () => {
+ it( 'should use the most inner value while converting nested font elements', () => {
+ const data = 'foo
';
+
+ editor.setData( data );
+
+ expect( getModelData( doc ) ).to.equal(
+ '<$text fontColor="#ff0000">[]foo$text> '
+ );
+ expect( editor.getData() ).to.equal(
+ 'foo
'
+ );
+ } );
+
+ it( 'should use the most inner value while converting a span with defined the color styles', () => {
+ const data = 'foo
';
+
+ editor.setData( data );
+
+ expect( getModelData( doc ) ).to.equal(
+ '<$text fontColor="#ff0000">[]foo$text> '
+ );
+ expect( editor.getData() ).to.equal(
+ 'foo
'
+ );
+ } );
+
+ it( 'should use the most inner value while converting the font element inside a span', () => {
+ const data = 'foo
';
+
+ editor.setData( data );
+
+ expect( getModelData( doc ) ).to.equal(
+ '<$text fontColor="#00ffff">[]foo$text> '
+ );
+ expect( editor.getData() ).to.equal(
+ 'foo
'
+ );
+ } );
+
+ it( 'should use the most inner value while converting nested spans with the color styles', () => {
+ const data = 'foo
';
+
+ editor.setData( data );
+
+ expect( getModelData( doc ) ).to.equal(
+ '<$text fontColor="#ff0000">[]foo$text> '
+ );
+ expect( editor.getData() ).to.equal(
+ 'foo
'
+ );
+ } );
+ } );
} );
} );
diff --git a/packages/ckeditor5-heading/docs/_snippets/features/default-headings.js b/packages/ckeditor5-heading/docs/_snippets/features/default-headings.js
index 9a2644cd15b..e1324732454 100644
--- a/packages/ckeditor5-heading/docs/_snippets/features/default-headings.js
+++ b/packages/ckeditor5-heading/docs/_snippets/features/default-headings.js
@@ -30,7 +30,10 @@ ClassicEditor
editor.ui.view.toolbar, item => item.buttonView && item.buttonView.label && item.buttonView.label.startsWith( 'Heading' )
),
text: 'Click to change heading level.',
- editor
+ editor,
+ tippyOptions: {
+ placement: 'bottom-start'
+ }
} );
} )
.catch( err => {
diff --git a/packages/ckeditor5-heading/docs/_snippets/features/heading-buttons.js b/packages/ckeditor5-heading/docs/_snippets/features/heading-buttons.js
index 2b29e262779..59ff2ee13fe 100644
--- a/packages/ckeditor5-heading/docs/_snippets/features/heading-buttons.js
+++ b/packages/ckeditor5-heading/docs/_snippets/features/heading-buttons.js
@@ -23,7 +23,10 @@ ClassicEditor
editor.ui.view.toolbar, item => item.label && item.label === 'Heading 1'
),
text: 'Click to choose heading level.',
- editor
+ editor,
+ tippyOptions: {
+ placement: 'bottom-start'
+ }
} );
} )
.catch( err => {
diff --git a/packages/ckeditor5-heading/docs/features/headings.md b/packages/ckeditor5-heading/docs/features/headings.md
index cce76280356..28921763b4d 100644
--- a/packages/ckeditor5-heading/docs/features/headings.md
+++ b/packages/ckeditor5-heading/docs/features/headings.md
@@ -43,7 +43,7 @@ The heading feature lets you also use a set of heading buttons instead of the dr
## Related features
There are more CKEditor 5 features that can help you format your content:
-* {@link features/basic-styles Basic font styles} – The essentials, like **bold**, *italic* and others.
+* {@link features/basic-styles Basic text styles} – The essentials, like **bold**, *italic* and others.
* {@link features/title Document title} – Clearly divide your content into a title and body.
* {@link features/indent Block indentation} – Set indentation for text blocks such as paragraphs or lists.
* {@link features/lists Lists} – Organize your content better with ordered and unordered lists you can style.
diff --git a/packages/ckeditor5-heading/lang/translations/nl.po b/packages/ckeditor5-heading/lang/translations/nl.po
index 9b94991764a..b70113fe4d1 100644
--- a/packages/ckeditor5-heading/lang/translations/nl.po
+++ b/packages/ckeditor5-heading/lang/translations/nl.po
@@ -58,4 +58,4 @@ msgstr "Voor uw titel in"
msgctxt "A default value of the placeholder for the content body."
msgid "Type or paste your content here."
-msgstr "Voer or plak uw inhoud in"
+msgstr "Voer of plak uw inhoud in."
diff --git a/packages/ckeditor5-heading/lang/translations/tk.po b/packages/ckeditor5-heading/lang/translations/tk.po
index 1f59117a546..757fa071f82 100644
--- a/packages/ckeditor5-heading/lang/translations/tk.po
+++ b/packages/ckeditor5-heading/lang/translations/tk.po
@@ -54,7 +54,7 @@ msgstr "Sözbaşy 6"
msgctxt "A default value of the placeholder for the content title."
msgid "Type your title"
-msgstr "Adyňyzy ýazyň"
+msgstr "Sözbaşyny ýazyň"
msgctxt "A default value of the placeholder for the content body."
msgid "Type or paste your content here."
diff --git a/packages/ckeditor5-highlight/docs/features/highlight.md b/packages/ckeditor5-highlight/docs/features/highlight.md
index 54689c572e7..adf9c0221ea 100644
--- a/packages/ckeditor5-highlight/docs/features/highlight.md
+++ b/packages/ckeditor5-highlight/docs/features/highlight.md
@@ -17,7 +17,7 @@ The highlight plugin always comes with a predefined and limited number of availa
## Related features
There are more CKEditor 5 features that can help you style your content:
-* {@link features/basic-styles Basic font styles} – The essentials, like **bold**, *italic* and others.
+* {@link features/basic-styles Basic text styles} – The essentials, like **bold**, *italic* and others.
* {@link features/font Font styles} – Easily and efficiently control the font {@link features/font#configuring-the-font-family-feature family}, {@link features/font#configuring-the-font-size-feature size}, {@link features/font#configuring-the-font-color-and-font-background-color-features text or background color}.
* {@link features/block-quote Block quote} – Include block quotations or pull quotes in your rich-text content.
* {@link features/remove-format Remove format} – Easily clean basic text formatting.
diff --git a/packages/ckeditor5-highlight/tests/highlightediting.js b/packages/ckeditor5-highlight/tests/highlightediting.js
index 5d46aaa6371..4e4730facef 100644
--- a/packages/ckeditor5-highlight/tests/highlightediting.js
+++ b/packages/ckeditor5-highlight/tests/highlightediting.js
@@ -54,11 +54,12 @@ describe( 'HighlightEditing', () => {
expect( editor.getData() ).to.equal( data );
} );
- it( 'should convert only one defined marker classes', () => {
- editor.setData( 'foo
' );
+ // After closing #8921, converted will be the last class in the alphabetical order that matches the configuration options.
+ it( 'should convert only one class even if the marker has a few of them', () => {
+ editor.setData( 'foo
' );
- expect( getModelData( model ) ).to.equal( '[]f<$text highlight="greenMarker">o$text>o ' );
- expect( editor.getData() ).to.equal( 'foo
' );
+ expect( getModelData( model ) ).to.equal( '[]f<$text highlight="yellowMarker">o$text>o ' );
+ expect( editor.getData() ).to.equal( 'foo
' );
} );
it( 'should not convert undefined marker classes', () => {
diff --git a/packages/ckeditor5-horizontal-line/lang/translations/de-ch.po b/packages/ckeditor5-horizontal-line/lang/translations/de-ch.po
new file mode 100644
index 00000000000..09289d0785f
--- /dev/null
+++ b/packages/ckeditor5-horizontal-line/lang/translations/de-ch.po
@@ -0,0 +1,21 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: German (Switzerland) (https://www.transifex.com/ckeditor/teams/11143/de_CH/)\n"
+"Language: de_CH\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Horizontal line"
+msgid "Horizontal line"
+msgstr "Horizontale Linie"
diff --git a/packages/ckeditor5-html-embed/lang/translations/cs.po b/packages/ckeditor5-html-embed/lang/translations/cs.po
new file mode 100644
index 00000000000..4aaad1278dd
--- /dev/null
+++ b/packages/ckeditor5-html-embed/lang/translations/cs.po
@@ -0,0 +1,45 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Czech (https://www.transifex.com/ckeditor/teams/11143/cs/)\n"
+"Language: cs\n"
+"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
+
+msgctxt "Toolbar button tooltip for the HTML embed feature."
+msgid "Insert HTML"
+msgstr "Vložit kód HTML"
+
+msgctxt "The HTML snippet."
+msgid "HTML snippet"
+msgstr "Kód HTML"
+
+msgctxt "A placeholder that will be displayed in the raw HTML textarea field."
+msgid "Paste raw HTML here..."
+msgstr "Sem vložte kód HTML ..."
+
+msgctxt "A label of a button that switches the HTML embed to the source editing mode."
+msgid "Edit source"
+msgstr "Upravit zdroj"
+
+msgctxt "A label of a button that saves the HTML embed content and navigates back to the preview."
+msgid "Save changes"
+msgstr "Uložit změny"
+
+msgctxt "An information displayed in the HTML embed preview if the content is not previewable."
+msgid "No preview available"
+msgstr "Náhled není k dispozici"
+
+msgctxt "An information displayed in the HTML embed preview if the HTML snippet has no content."
+msgid "Empty snippet content"
+msgstr "Prázdný obsah kódu"
diff --git a/packages/ckeditor5-html-embed/lang/translations/sk.po b/packages/ckeditor5-html-embed/lang/translations/sk.po
new file mode 100644
index 00000000000..77ebb2a735b
--- /dev/null
+++ b/packages/ckeditor5-html-embed/lang/translations/sk.po
@@ -0,0 +1,45 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Slovak (https://www.transifex.com/ckeditor/teams/11143/sk/)\n"
+"Language: sk\n"
+"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n"
+
+msgctxt "Toolbar button tooltip for the HTML embed feature."
+msgid "Insert HTML"
+msgstr "Vložiť kód HTML"
+
+msgctxt "The HTML snippet."
+msgid "HTML snippet"
+msgstr "Kód HTML"
+
+msgctxt "A placeholder that will be displayed in the raw HTML textarea field."
+msgid "Paste raw HTML here..."
+msgstr "Sem vložte kód HTML..."
+
+msgctxt "A label of a button that switches the HTML embed to the source editing mode."
+msgid "Edit source"
+msgstr "Upraviť zdroj"
+
+msgctxt "A label of a button that saves the HTML embed content and navigates back to the preview."
+msgid "Save changes"
+msgstr "Uložiť zmeny"
+
+msgctxt "An information displayed in the HTML embed preview if the content is not previewable."
+msgid "No preview available"
+msgstr "Náhľad nie je k dispozícii"
+
+msgctxt "An information displayed in the HTML embed preview if the HTML snippet has no content."
+msgid "Empty snippet content"
+msgstr "Prázdny obsah kódu"
diff --git a/packages/ckeditor5-html-embed/lang/translations/uk.po b/packages/ckeditor5-html-embed/lang/translations/uk.po
index d8790623478..1f952d527c6 100644
--- a/packages/ckeditor5-html-embed/lang/translations/uk.po
+++ b/packages/ckeditor5-html-embed/lang/translations/uk.po
@@ -38,8 +38,8 @@ msgstr "Зберегти зміни"
msgctxt "An information displayed in the HTML embed preview if the content is not previewable."
msgid "No preview available"
-msgstr ""
+msgstr "Попередній перегляд недоступний"
msgctxt "An information displayed in the HTML embed preview if the HTML snippet has no content."
msgid "Empty snippet content"
-msgstr ""
+msgstr "Порожній вміст"
diff --git a/packages/ckeditor5-image/docs/framework/guides/deep-dive/upload-adapter.md b/packages/ckeditor5-image/docs/framework/guides/deep-dive/upload-adapter.md
index 2b2ee407ed1..b2d3d90b1f0 100644
--- a/packages/ckeditor5-image/docs/framework/guides/deep-dive/upload-adapter.md
+++ b/packages/ckeditor5-image/docs/framework/guides/deep-dive/upload-adapter.md
@@ -372,6 +372,22 @@ xhr.addEventListener( 'load', () => {
} );
```
+### Passing additional data to the response
+
+There is a chance you might need to pass additional data from the server to provide additional data to some features. In order to do that you need to wrap all URLs in the `urls` property and pass additional data in the top level of the object.
+
+For image uploading, you can later retrieve the data in the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing#event:uploadComplete `uploadComplete`} event, which allows setting new attributes and overriding the existing ones on the model image based on the data just after the image is uploaded.
+
+```js
+{
+ urls: {
+ default: 'http://example.com/images/image–default-size.png',
+ // Optional different sizes of images.
+ },
+ customProperty: 'foo'
+}
+```
+
### Activating a custom upload adapter
Having implemented the adapter, you must figure out how to enable it in the WYSIWYG editor. The good news is that it is pretty easy, and you do not need to {@link builds/guides/development/custom-builds rebuild the editor} to do that!
diff --git a/packages/ckeditor5-image/lang/translations/cs.po b/packages/ckeditor5-image/lang/translations/cs.po
index 0e965ad7c5d..65db1dcf202 100644
--- a/packages/ckeditor5-image/lang/translations/cs.po
+++ b/packages/ckeditor5-image/lang/translations/cs.po
@@ -66,36 +66,36 @@ msgstr "Panel nástrojů obrázku"
msgctxt "The label used for the dropdown in the image toolbar containing defined resize options."
msgid "Resize image"
-msgstr ""
+msgstr "Změnit velikost"
msgctxt "The label used for the standalone resize options buttons in the image toolbar."
msgid "Resize image to %0"
-msgstr ""
+msgstr "Změnit velikost na %0"
msgctxt "The accessibility label of the standalone image resize reset option button in the image toolbar for screen readers."
msgid "Resize image to the original size"
-msgstr ""
+msgstr "Změnit velikost na původní velikost"
msgctxt "The default label for the resize option that resets the size of the image."
msgid "Original"
-msgstr ""
+msgstr "Originální"
msgctxt "The accessibility label of the image resize dropdown for screen readers."
msgid "Image resize list"
-msgstr ""
+msgstr "Seznam možností změny velikosti"
msgctxt "The label of the form submit button if the image source URL input has no value."
msgid "Insert"
-msgstr ""
+msgstr "Vložit"
msgctxt "The label of the form submit button if the image source URL input has a value."
msgid "Update"
-msgstr ""
+msgstr "Aktualizovat"
msgctxt "The input label for the Insert image via URL form."
msgid "Insert image via URL"
-msgstr ""
+msgstr "Vložit obrázek pomocí URL"
msgctxt "The input label for the Insert image via URL form for a pre-existing image."
msgid "Update image URL"
-msgstr ""
+msgstr "Aktualizovat URL obrázku"
diff --git a/packages/ckeditor5-image/src/imageupload/imageuploadediting.js b/packages/ckeditor5-image/src/imageupload/imageuploadediting.js
index 51af7495860..c89b9b29ab9 100644
--- a/packages/ckeditor5-image/src/imageupload/imageuploadediting.js
+++ b/packages/ckeditor5-image/src/imageupload/imageuploadediting.js
@@ -25,6 +25,9 @@ import { getViewImgFromWidget } from '../image/utils';
* The editing part of the image upload feature. It registers the `'uploadImage'` command
* and `imageUpload` command as an aliased name.
*
+ * When an image is uploaded it fires the {@link ~ImageUploadEditing#event:uploadComplete `uploadComplete` event}
+ * that allows adding custom attributes to the {@link module:engine/model/element~Element image element}.
+ *
* @extends module:core/plugin~Plugin
*/
export default class ImageUploadEditing extends Plugin {
@@ -192,6 +195,16 @@ export default class ImageUploadEditing extends Plugin {
}
}
} );
+
+ // Set the default handler for feeding the image element with `src` and `srcset` attributes.
+ this.on( 'uploadComplete', ( evt, { imageElement, data } ) => {
+ const urls = data.urls ? data.urls : data;
+
+ this.editor.model.change( writer => {
+ writer.setAttribute( 'src', urls.default, imageElement );
+ this._parseAndSetSrcsetAttributeOnImage( urls, imageElement, writer );
+ } );
+ }, { priority: 'low' } );
}
/**
@@ -260,8 +273,37 @@ export default class ImageUploadEditing extends Plugin {
} )
.then( data => {
model.enqueueChange( 'transparent', writer => {
- writer.setAttributes( { uploadStatus: 'complete', src: data.default }, imageElement );
- this._parseAndSetSrcsetAttributeOnImage( data, imageElement, writer );
+ writer.setAttribute( 'uploadStatus', 'complete', imageElement );
+
+ /**
+ * An event fired when an image is uploaded. You can hook into this event to provide
+ * custom attributes to the {@link module:engine/model/element~Element image element} based on the data from
+ * the server.
+ *
+ * const imageUploadEditing = editor.plugins.get( 'ImageUploadEditing' );
+ *
+ * imageUploadEditing.on( 'uploadComplete', ( evt, { data, imageElement } ) => {
+ * editor.model.change( writer => {
+ * writer.setAttribute( 'someAttribute', 'foo', imageElement );
+ * } );
+ * } );
+ *
+ * You can also stop the default handler that sets the `src` and `srcset` attributes
+ * if you want to provide custom values for these attributes.
+ *
+ * imageUploadEditing.on( 'uploadComplete', ( evt, { data, imageElement } ) => {
+ * evt.stop();
+ * } );
+ *
+ * **Note**: This event is fired by the {@link module:image/imageupload/imageuploadediting~ImageUploadEditing} plugin.
+ *
+ * @event uploadComplete
+ * @param {Object} data The `uploadComplete` event data.
+ * @param {Object} data.data The data coming from the upload adapter.
+ * @param {module:engine/model/element~Element} data.imageElement The
+ * model {@link module:engine/model/element~Element image element} that can be customized.
+ */
+ this.fire( 'uploadComplete', { data, imageElement } );
} );
clean();
@@ -312,7 +354,7 @@ export default class ImageUploadEditing extends Plugin {
let maxWidth = 0;
const srcsetAttribute = Object.keys( data )
- // Filter out keys that are not integers.
+ // Filter out keys that are not integers.
.filter( key => {
const width = parseInt( key, 10 );
diff --git a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js
index d424c4fe231..3ac7ecfe572 100644
--- a/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js
+++ b/packages/ckeditor5-image/tests/imageupload/imageuploadediting.js
@@ -25,6 +25,7 @@ import { setData as setModelData, getData as getModelData } from '@ckeditor/cked
import { getData as getViewData, stringify as stringifyView } from '@ckeditor/ckeditor5-engine/src/dev-utils/view';
import Notification from '@ckeditor/ckeditor5-ui/src/notification/notification';
+import { modelToViewAttributeConverter } from '../../src/image/converters';
describe( 'ImageUploadEditing', () => {
// eslint-disable-next-line max-len
@@ -381,9 +382,9 @@ describe( 'ImageUploadEditing', () => {
tryExpect( done, () => {
expect( getViewData( view ) ).to.equal(
'[]' +
+ // Rendering the image data is left to a upload progress converter.
+ '' +
+ ']' +
'foo bar
'
);
@@ -396,25 +397,46 @@ describe( 'ImageUploadEditing', () => {
loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
} );
- it( 'should replace read data with server response once it is present', done => {
+ it( 'should replace read data with server response once it is present', async () => {
const file = createNativeFileMock();
setModelData( model, '{}foo bar ' );
editor.execute( 'uploadImage', { file } );
- model.document.once( 'change', () => {
- model.document.once( 'change', () => {
- tryExpect( done, () => {
- expect( getViewData( view ) ).to.equal(
- '[]foo bar
'
- );
- expect( loader.status ).to.equal( 'idle' );
- } );
- }, { priority: 'lowest' } );
+ await new Promise( res => {
+ model.document.once( 'change', res );
+ loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
+ } );
+ await new Promise( res => {
+ model.document.once( 'change', res, { priority: 'lowest' } );
loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { default: 'image.png' } ) );
} );
- loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
+ expect( getViewData( view ) ).to.equal(
+ '[]foo bar
'
+ );
+ expect( loader.status ).to.equal( 'idle' );
+ } );
+
+ it( 'should support adapter response with the normalized `urls` property', async () => {
+ const file = createNativeFileMock();
+ setModelData( model, '{}foo bar ' );
+ editor.execute( 'uploadImage', { file } );
+
+ await new Promise( res => {
+ model.document.once( 'change', res );
+ loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
+ } );
+
+ await new Promise( res => {
+ model.document.once( 'change', res, { priority: 'lowest' } );
+ loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { urls: { default: 'image.png' } } ) );
+ } );
+
+ expect( getViewData( view ) ).to.equal(
+ '[]foo bar
'
+ );
+ expect( loader.status ).to.equal( 'idle' );
} );
it( 'should fire notification event in case of error', done => {
@@ -605,7 +627,7 @@ describe( 'ImageUploadEditing', () => {
} );
} );
- it( 'should create responsive image if server return multiple images', done => {
+ it( 'should create responsive image if the server returns multiple images', done => {
const file = createNativeFileMock();
setModelData( model, '{}foo bar ' );
editor.execute( 'uploadImage', { file } );
@@ -628,6 +650,162 @@ describe( 'ImageUploadEditing', () => {
loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
} );
+ describe( 'uploadComplete event', () => {
+ it( 'should be fired when the upload adapter resolves with the image data', async () => {
+ const file = createNativeFileMock();
+ setModelData( model, '[]foo bar ' );
+
+ const imageUploadEditing = editor.plugins.get( 'ImageUploadEditing' );
+ const uploadCompleteSpy = sinon.spy();
+
+ imageUploadEditing.on( 'uploadComplete', uploadCompleteSpy );
+
+ editor.execute( 'uploadImage', { file } );
+
+ await new Promise( res => {
+ model.document.once( 'change', res );
+ loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
+ } );
+
+ sinon.assert.notCalled( uploadCompleteSpy );
+
+ await new Promise( res => {
+ model.document.once( 'change', res, { priority: 'lowest' } );
+ loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { default: 'image.png' } ) );
+ } );
+
+ sinon.assert.calledOnce( uploadCompleteSpy );
+
+ const eventArgs = uploadCompleteSpy.firstCall.args[ 1 ];
+
+ expect( eventArgs ).to.be.an( 'object' );
+ expect( eventArgs.imageElement.is( 'model:element', 'image' ) ).to.be.true;
+ expect( eventArgs.data ).to.deep.equal( { default: 'image.png' } );
+ } );
+
+ it( 'should allow modifying the image element once the original image is uploaded', async () => {
+ const file = createNativeFileMock();
+ setModelData( model, '[]foo bar ' );
+
+ editor.model.schema.extend( 'image', { allowAttributes: 'data-original' } );
+
+ editor.conversion.for( 'downcast' )
+ .add( modelToViewAttributeConverter( 'data-original' ) );
+
+ editor.conversion.for( 'upcast' )
+ .attributeToAttribute( {
+ view: {
+ name: 'img',
+ key: 'data-original'
+ },
+ model: 'data-original'
+ } );
+
+ const imageUploadEditing = editor.plugins.get( 'ImageUploadEditing' );
+ let batch;
+
+ imageUploadEditing.on( 'uploadComplete', ( evt, { imageElement, data } ) => {
+ editor.model.change( writer => {
+ writer.setAttribute( 'data-original', data.originalUrl, imageElement );
+ batch = writer.batch;
+ } );
+ } );
+
+ editor.execute( 'uploadImage', { file } );
+
+ await new Promise( res => {
+ model.document.once( 'change', res );
+ loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
+ } );
+
+ await new Promise( res => {
+ model.document.once( 'change', res, { priority: 'lowest' } );
+ loader.file.then( () => adapterMocks[ 0 ].mockSuccess( { originalUrl: 'original.jpg', default: 'image.jpg' } ) );
+ } );
+
+ // Make sure the custom attribute was set in the same transparent batch as the default handling (setting src and status).
+ expect( batch.type ).to.equal( 'transparent' );
+ expect( batch.operations.length ).to.equal( 3 );
+
+ expect( batch.operations[ 0 ].type ).to.equal( 'changeAttribute' );
+ expect( batch.operations[ 0 ].key ).to.equal( 'uploadStatus' );
+ expect( batch.operations[ 0 ].newValue ).to.equal( 'complete' );
+
+ expect( batch.operations[ 1 ].type ).to.equal( 'addAttribute' );
+ expect( batch.operations[ 1 ].key ).to.equal( 'data-original' );
+ expect( batch.operations[ 1 ].newValue ).to.equal( 'original.jpg' );
+
+ expect( batch.operations[ 2 ].type ).to.equal( 'addAttribute' );
+ expect( batch.operations[ 2 ].key ).to.equal( 'src' );
+ expect( batch.operations[ 2 ].newValue ).to.equal( 'image.jpg' );
+
+ expect( getModelData( model ) ).to.equal(
+ '[ ]foo bar '
+ );
+
+ expect( getViewData( view ) ).to.equal(
+ '[]' +
+ 'foo bar
'
+ );
+ } );
+
+ it( 'should allow stopping the original listener that sets image attributes based on the data', async () => {
+ const file = createNativeFileMock();
+ setModelData( model, '[]foo bar ' );
+
+ const imageUploadEditing = editor.plugins.get( 'ImageUploadEditing' );
+ let batch;
+
+ imageUploadEditing.on( 'uploadComplete', ( evt, { imageElement } ) => {
+ evt.stop();
+
+ model.change( writer => {
+ writer.setAttribute( 'src', 'foo.jpg', imageElement );
+ batch = writer.batch;
+ } );
+ } );
+
+ editor.execute( 'uploadImage', { file } );
+
+ await new Promise( res => {
+ model.document.once( 'change', res );
+ loader.file.then( () => nativeReaderMock.mockSuccess( base64Sample ) );
+ } );
+
+ await new Promise( res => {
+ model.document.once( 'change', res, { priority: 'lowest' } );
+ loader.file.then( () => adapterMocks[ 0 ].mockSuccess(
+ { default: 'image.png', 500: 'image-500.png', 800: 'image-800.png' }
+ ) );
+ } );
+
+ // Make sure the custom attribute was set in the same transparent batch as the default handling (setting src and status).
+ expect( batch.type ).to.equal( 'transparent' );
+ expect( batch.operations.length ).to.equal( 2 );
+
+ expect( batch.operations[ 0 ].type ).to.equal( 'changeAttribute' );
+ expect( batch.operations[ 0 ].key ).to.equal( 'uploadStatus' );
+ expect( batch.operations[ 0 ].newValue ).to.equal( 'complete' );
+
+ expect( batch.operations[ 1 ].type ).to.equal( 'addAttribute' );
+ expect( batch.operations[ 1 ].key ).to.equal( 'src' );
+ expect( batch.operations[ 1 ].newValue ).to.equal( 'foo.jpg' );
+
+ expect( getModelData( model ) ).to.equal(
+ '[ ]foo bar '
+ );
+
+ expect( getViewData( view ) ).to.equal(
+ '[]' +
+ 'foo bar
'
+ );
+ } );
+ } );
+
it( 'should prevent from browser redirecting when an image is dropped on another image', () => {
const spy = sinon.spy();
diff --git a/packages/ckeditor5-language/lang/translations/cs.po b/packages/ckeditor5-language/lang/translations/cs.po
new file mode 100644
index 00000000000..d105d334a0b
--- /dev/null
+++ b/packages/ckeditor5-language/lang/translations/cs.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Czech (https://www.transifex.com/ckeditor/teams/11143/cs/)\n"
+"Language: cs\n"
+"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
+
+msgctxt "Toolbar button tooltip for the text part language feature."
+msgid "Language"
+msgstr "Jazyk"
+
+msgctxt "Default label for the text part language dropdown."
+msgid "Choose language"
+msgstr "Vybrat jazyk"
+
+msgctxt "The label of the remove language option for the text part language dropdown."
+msgid "Remove language"
+msgstr "Odstranit jazyk"
diff --git a/packages/ckeditor5-language/lang/translations/fr.po b/packages/ckeditor5-language/lang/translations/fr.po
new file mode 100644
index 00000000000..e7e0a3d7e1f
--- /dev/null
+++ b/packages/ckeditor5-language/lang/translations/fr.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: French (https://www.transifex.com/ckeditor/teams/11143/fr/)\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+
+msgctxt "Toolbar button tooltip for the text part language feature."
+msgid "Language"
+msgstr "Langue"
+
+msgctxt "Default label for the text part language dropdown."
+msgid "Choose language"
+msgstr "Choisir la langue"
+
+msgctxt "The label of the remove language option for the text part language dropdown."
+msgid "Remove language"
+msgstr "Supprimer la langue"
diff --git a/packages/ckeditor5-language/lang/translations/it.po b/packages/ckeditor5-language/lang/translations/it.po
new file mode 100644
index 00000000000..1c557b83cee
--- /dev/null
+++ b/packages/ckeditor5-language/lang/translations/it.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Italian (https://www.transifex.com/ckeditor/teams/11143/it/)\n"
+"Language: it\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the text part language feature."
+msgid "Language"
+msgstr "Lingua"
+
+msgctxt "Default label for the text part language dropdown."
+msgid "Choose language"
+msgstr "Seleziona lingua"
+
+msgctxt "The label of the remove language option for the text part language dropdown."
+msgid "Remove language"
+msgstr "Rimuovi lingua"
diff --git a/packages/ckeditor5-language/lang/translations/nl.po b/packages/ckeditor5-language/lang/translations/nl.po
new file mode 100644
index 00000000000..264262de382
--- /dev/null
+++ b/packages/ckeditor5-language/lang/translations/nl.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Dutch (https://www.transifex.com/ckeditor/teams/11143/nl/)\n"
+"Language: nl\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the text part language feature."
+msgid "Language"
+msgstr "Taal"
+
+msgctxt "Default label for the text part language dropdown."
+msgid "Choose language"
+msgstr "Kies taal"
+
+msgctxt "The label of the remove language option for the text part language dropdown."
+msgid "Remove language"
+msgstr "Taal verwijderen"
diff --git a/packages/ckeditor5-language/lang/translations/sk.po b/packages/ckeditor5-language/lang/translations/sk.po
new file mode 100644
index 00000000000..e0dac20db0f
--- /dev/null
+++ b/packages/ckeditor5-language/lang/translations/sk.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Slovak (https://www.transifex.com/ckeditor/teams/11143/sk/)\n"
+"Language: sk\n"
+"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n == 1 ? 0 : n % 1 == 0 && n >= 2 && n <= 4 ? 1 : n % 1 != 0 ? 2: 3);\n"
+
+msgctxt "Toolbar button tooltip for the text part language feature."
+msgid "Language"
+msgstr "Jazyk"
+
+msgctxt "Default label for the text part language dropdown."
+msgid "Choose language"
+msgstr "Vybrať jazyk"
+
+msgctxt "The label of the remove language option for the text part language dropdown."
+msgid "Remove language"
+msgstr "Odstrániť jazyk"
diff --git a/packages/ckeditor5-language/lang/translations/tk.po b/packages/ckeditor5-language/lang/translations/tk.po
new file mode 100644
index 00000000000..93f0b933d24
--- /dev/null
+++ b/packages/ckeditor5-language/lang/translations/tk.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Turkmen (https://www.transifex.com/ckeditor/teams/11143/tk/)\n"
+"Language: tk\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+msgctxt "Toolbar button tooltip for the text part language feature."
+msgid "Language"
+msgstr "Dil"
+
+msgctxt "Default label for the text part language dropdown."
+msgid "Choose language"
+msgstr "Dili saýlaň"
+
+msgctxt "The label of the remove language option for the text part language dropdown."
+msgid "Remove language"
+msgstr "Dili pozuň"
diff --git a/packages/ckeditor5-language/lang/translations/uk.po b/packages/ckeditor5-language/lang/translations/uk.po
new file mode 100644
index 00000000000..3192eed2b57
--- /dev/null
+++ b/packages/ckeditor5-language/lang/translations/uk.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Ukrainian (https://www.transifex.com/ckeditor/teams/11143/uk/)\n"
+"Language: uk\n"
+"Plural-Forms: nplurals=4; plural=(n % 1 == 0 && n % 10 == 1 && n % 100 != 11 ? 0 : n % 1 == 0 && n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 12 || n % 100 > 14) ? 1 : n % 1 == 0 && (n % 10 ==0 || (n % 10 >=5 && n % 10 <=9) || (n % 100 >=11 && n % 100 <=14 )) ? 2: 3);\n"
+
+msgctxt "Toolbar button tooltip for the text part language feature."
+msgid "Language"
+msgstr "Мова"
+
+msgctxt "Default label for the text part language dropdown."
+msgid "Choose language"
+msgstr "Обрати мову"
+
+msgctxt "The label of the remove language option for the text part language dropdown."
+msgid "Remove language"
+msgstr "Видалити мову"
diff --git a/packages/ckeditor5-language/lang/translations/zh.po b/packages/ckeditor5-language/lang/translations/zh.po
new file mode 100644
index 00000000000..fac3340b598
--- /dev/null
+++ b/packages/ckeditor5-language/lang/translations/zh.po
@@ -0,0 +1,29 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Chinese (Taiwan) (https://www.transifex.com/ckeditor/teams/11143/zh_TW/)\n"
+"Language: zh_TW\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+msgctxt "Toolbar button tooltip for the text part language feature."
+msgid "Language"
+msgstr "語言"
+
+msgctxt "Default label for the text part language dropdown."
+msgid "Choose language"
+msgstr "選擇語言"
+
+msgctxt "The label of the remove language option for the text part language dropdown."
+msgid "Remove language"
+msgstr "移除語言"
diff --git a/packages/ckeditor5-language/tests/textpartlanguageediting.js b/packages/ckeditor5-language/tests/textpartlanguageediting.js
index 0d3a829b11a..e30fb67fdb3 100644
--- a/packages/ckeditor5-language/tests/textpartlanguageediting.js
+++ b/packages/ckeditor5-language/tests/textpartlanguageediting.js
@@ -82,6 +82,21 @@ describe( 'TextPartLanguageEditing', () => {
expect( editor.getData() ).to.equal( 'foobar
' );
} );
+
+ it( 'should respect nested element language ', () => {
+ editor.setData( 'hebrewfrenchhebrew
' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( '' +
+ '<$text language="he:rtl">hebrew$text>' +
+ '<$text language="fr:ltr">french$text>' +
+ '<$text language="he:rtl">hebrew$text> ' );
+
+ expect( editor.getData() ).to.equal( '' +
+ 'hebrew' +
+ 'french' +
+ 'hebrew
' );
+ } );
} );
describe( 'editing pipeline conversion', () => {
diff --git a/packages/ckeditor5-link/lang/translations/cs.po b/packages/ckeditor5-link/lang/translations/cs.po
index 42df7a71d6d..b2ec4d296d2 100644
--- a/packages/ckeditor5-link/lang/translations/cs.po
+++ b/packages/ckeditor5-link/lang/translations/cs.po
@@ -30,7 +30,7 @@ msgstr "URL odkazu"
msgctxt "Label for the image link button."
msgid "Link image"
-msgstr ""
+msgstr "Adresa obrázku"
msgctxt "Button opening the Link URL editing balloon."
msgid "Edit link"
diff --git a/packages/ckeditor5-link/lang/translations/nl.po b/packages/ckeditor5-link/lang/translations/nl.po
index 2eea284cbac..85384d23fcd 100644
--- a/packages/ckeditor5-link/lang/translations/nl.po
+++ b/packages/ckeditor5-link/lang/translations/nl.po
@@ -30,7 +30,7 @@ msgstr "Link URL"
msgctxt "Label for the image link button."
msgid "Link image"
-msgstr ""
+msgstr "Link afbeelding"
msgctxt "Button opening the Link URL editing balloon."
msgid "Edit link"
diff --git a/packages/ckeditor5-list/lang/translations/cs.po b/packages/ckeditor5-list/lang/translations/cs.po
index 48022fc7bf1..68ba68fd4bf 100644
--- a/packages/ckeditor5-list/lang/translations/cs.po
+++ b/packages/ckeditor5-list/lang/translations/cs.po
@@ -30,80 +30,80 @@ msgstr "Seznam úkolů"
msgctxt "The ARIA label of the toolbar displaying buttons allowing users to change the bulleted list style."
msgid "Bulleted list styles toolbar"
-msgstr ""
+msgstr "Panel seznamu s odrážkami"
msgctxt "The ARIA label of the toolbar displaying buttons allowing users to change the numbered list style."
msgid "Numbered list styles toolbar"
-msgstr ""
+msgstr "Panel se styly číslovaného seznamu"
msgctxt "The ARIA label of the button that toggles the \"disc\" list style."
msgid "Toggle the disc list style"
-msgstr ""
+msgstr "Přepnout na seznam s označením plného kruhu"
msgctxt "The ARIA label of the button that toggles the \"circle\" list style."
msgid "Toggle the circle list style"
-msgstr ""
+msgstr "Přepnout na seznam s kruhovým označením"
msgctxt "The ARIA label of the button that toggles the \"square\" list style."
msgid "Toggle the square list style"
-msgstr ""
+msgstr "Přepnout na seznam se čtvercovým označením"
msgctxt "The ARIA label of the button that toggles the \"decimal\" list style."
msgid "Toggle the decimal list style"
-msgstr ""
+msgstr "Přepnout na číselný seznam"
msgctxt "The ARIA label of the button that toggles the \"decimal with leading zero\" list style."
msgid "Toggle the decimal with leading zero list style"
-msgstr ""
+msgstr "Přepnout na číselný seznam s nulou na začátku"
msgctxt "The ARIA label of the button that toggles the \"lower–roman\" list style."
msgid "Toggle the lower–roman list style"
-msgstr ""
+msgstr "Přepnout na seznam s malými římskými čísly"
msgctxt "The ARIA label of the button that toggles the \"upper–roman\" list style."
msgid "Toggle the upper–roman list style"
-msgstr ""
+msgstr "Přepnout na seznam s velkými římskými čísly"
msgctxt "The ARIA label of the button that toggles the \"lower–latin\" list style."
msgid "Toggle the lower–latin list style"
-msgstr ""
+msgstr "Přepnout na seznam s malými písmeny"
msgctxt "The ARIA label of the button that toggles the \"upper–latin\" list style."
msgid "Toggle the upper–latin list style"
-msgstr ""
+msgstr "Přepnout na seznam s velkými písmeny"
msgctxt "The tooltip text of the button that toggles the \"disc\" list style."
msgid "Disc"
-msgstr ""
+msgstr "Plný kruh"
msgctxt "The tooltip text of the button that toggles the \"circle\" list style."
msgid "Circle"
-msgstr ""
+msgstr "Kruh"
msgctxt "The tooltip text of the button that toggles the \"square\" list style."
msgid "Square"
-msgstr ""
+msgstr "Čtverec"
msgctxt "The tooltip text of the button that toggles the \"decimal\" list style."
msgid "Decimal"
-msgstr ""
+msgstr "Čísla"
msgctxt "The tooltip text of the button that toggles the \"decimal with leading zero\" list style."
msgid "Decimal with leading zero"
-msgstr ""
+msgstr "Čísla s nulou na začátku"
msgctxt "The tooltip text of the button that toggles the \"lower–roman\" list style."
msgid "Lower–roman"
-msgstr ""
+msgstr "Malé římské čísla"
msgctxt "The tooltip text of the button that toggles the \"upper–roman\" list style."
msgid "Upper-roman"
-msgstr ""
+msgstr "Velké římské čísla"
msgctxt "The tooltip text of the button that toggles the \"lower–latin\" list style."
msgid "Lower-latin"
-msgstr ""
+msgstr "Malá písmena"
msgctxt "The tooltip text of the button that toggles the \"upper–latin\" list style."
msgid "Upper-latin"
-msgstr ""
+msgstr "Velká písmena"
diff --git a/packages/ckeditor5-list/lang/translations/sk.po b/packages/ckeditor5-list/lang/translations/sk.po
index 157c6611703..5262386bb56 100644
--- a/packages/ckeditor5-list/lang/translations/sk.po
+++ b/packages/ckeditor5-list/lang/translations/sk.po
@@ -30,80 +30,80 @@ msgstr "To-do zoznam"
msgctxt "The ARIA label of the toolbar displaying buttons allowing users to change the bulleted list style."
msgid "Bulleted list styles toolbar"
-msgstr ""
+msgstr "Panel zoznamu s odrážkami"
msgctxt "The ARIA label of the toolbar displaying buttons allowing users to change the numbered list style."
msgid "Numbered list styles toolbar"
-msgstr ""
+msgstr "Panel so štýlmi číslovaného zoznamu"
msgctxt "The ARIA label of the button that toggles the \"disc\" list style."
msgid "Toggle the disc list style"
-msgstr ""
+msgstr "Prepnúť na zoznam s označením plného kruhu"
msgctxt "The ARIA label of the button that toggles the \"circle\" list style."
msgid "Toggle the circle list style"
-msgstr ""
+msgstr "Prepnúť na zoznam s kruhovým označením"
msgctxt "The ARIA label of the button that toggles the \"square\" list style."
msgid "Toggle the square list style"
-msgstr ""
+msgstr "Prepnúť na zoznam so štvorcovým označením"
msgctxt "The ARIA label of the button that toggles the \"decimal\" list style."
msgid "Toggle the decimal list style"
-msgstr ""
+msgstr "Prepnúť na číselný zoznam"
msgctxt "The ARIA label of the button that toggles the \"decimal with leading zero\" list style."
msgid "Toggle the decimal with leading zero list style"
-msgstr ""
+msgstr "Prepnúť na číselný zoznam s nulou na začiatku"
msgctxt "The ARIA label of the button that toggles the \"lower–roman\" list style."
msgid "Toggle the lower–roman list style"
-msgstr ""
+msgstr "Prepnúť na zoznam s malými rímskymi číslami"
msgctxt "The ARIA label of the button that toggles the \"upper–roman\" list style."
msgid "Toggle the upper–roman list style"
-msgstr ""
+msgstr "Prepnúť na zoznam s veľkými rímskymi číslami"
msgctxt "The ARIA label of the button that toggles the \"lower–latin\" list style."
msgid "Toggle the lower–latin list style"
-msgstr ""
+msgstr "Prepnúť na zoznam s malými písmenami"
msgctxt "The ARIA label of the button that toggles the \"upper–latin\" list style."
msgid "Toggle the upper–latin list style"
-msgstr ""
+msgstr "Prepnúť na zoznam s veľkými písmenami"
msgctxt "The tooltip text of the button that toggles the \"disc\" list style."
msgid "Disc"
-msgstr ""
+msgstr "Plný kruh"
msgctxt "The tooltip text of the button that toggles the \"circle\" list style."
msgid "Circle"
-msgstr ""
+msgstr "Kruh"
msgctxt "The tooltip text of the button that toggles the \"square\" list style."
msgid "Square"
-msgstr ""
+msgstr "Štvorec"
msgctxt "The tooltip text of the button that toggles the \"decimal\" list style."
msgid "Decimal"
-msgstr ""
+msgstr "Čísla"
msgctxt "The tooltip text of the button that toggles the \"decimal with leading zero\" list style."
msgid "Decimal with leading zero"
-msgstr ""
+msgstr "Čísla s nulou na začiatku"
msgctxt "The tooltip text of the button that toggles the \"lower–roman\" list style."
msgid "Lower–roman"
-msgstr ""
+msgstr "Malé rímske čísla"
msgctxt "The tooltip text of the button that toggles the \"upper–roman\" list style."
msgid "Upper-roman"
-msgstr ""
+msgstr "Veľké rímske čísla"
msgctxt "The tooltip text of the button that toggles the \"lower–latin\" list style."
msgid "Lower-latin"
-msgstr ""
+msgstr "Malé písmená"
msgctxt "The tooltip text of the button that toggles the \"upper–latin\" list style."
msgid "Upper-latin"
-msgstr ""
+msgstr "Veľké písmená"
diff --git a/packages/ckeditor5-list/tests/listediting.js b/packages/ckeditor5-list/tests/listediting.js
index 9adbef52876..439e162b874 100644
--- a/packages/ckeditor5-list/tests/listediting.js
+++ b/packages/ckeditor5-list/tests/listediting.js
@@ -1646,7 +1646,15 @@ describe( 'ListEditing', () => {
'' +
'' +
'' +
- 'e
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ 'e ' +
+ ' ' +
+ '' +
+ '
' +
+ '' +
'' +
'' +
'' +
@@ -2003,7 +2011,13 @@ describe( 'ListEditing', () => {
'b ' +
'c ' +
'd ' +
- 'e ' +
+ '' +
+ '' +
+ '' +
+ 'e ' +
+ ' ' +
+ ' ' +
+ '
' +
'' +
'' +
'' +
diff --git a/packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js b/packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js
index 7bc6f498fbb..485b513047e 100644
--- a/packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js
+++ b/packages/ckeditor5-markdown-gfm/src/gfmdataprocessor.js
@@ -83,4 +83,10 @@ export default class GFMDataProcessor {
registerRawContentMatcher( pattern ) {
this._htmlDP.registerRawContentMatcher( pattern );
}
+
+ /**
+ * This method does not have an effect on data processor result. It exists for compatibility with
+ * {@link module:engine/dataprocessor/dataprocessor~DataProcessor `DataProcessor` interface}.
+ */
+ useFillerType() {}
}
diff --git a/packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/gfmdataprocessor.js b/packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/gfmdataprocessor.js
new file mode 100644
index 00000000000..8fa6d261862
--- /dev/null
+++ b/packages/ckeditor5-markdown-gfm/tests/gfmdataprocessor/gfmdataprocessor.js
@@ -0,0 +1,25 @@
+/**
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+import GFMDataProcessor from '../../src/gfmdataprocessor';
+import ViewDocument from '@ckeditor/ckeditor5-engine/src/view/document';
+import { StylesProcessor } from '@ckeditor/ckeditor5-engine/src/view/stylesmap';
+
+describe( 'GFMDataProcessor', () => {
+ let dataProcessor, viewDocument;
+
+ beforeEach( () => {
+ viewDocument = new ViewDocument( new StylesProcessor() );
+ dataProcessor = new GFMDataProcessor( viewDocument );
+ } );
+
+ describe( 'useFillerType()', () => {
+ it( 'should have this method to be compatible with `DataProcessor` interface', () => {
+ expect( () => {
+ dataProcessor.useFillerType( 'default' );
+ } ).not.to.throw();
+ } );
+ } );
+} );
diff --git a/packages/ckeditor5-media-embed/docs/features/media-embed.md b/packages/ckeditor5-media-embed/docs/features/media-embed.md
index 931c5442e7b..ffac32038e2 100644
--- a/packages/ckeditor5-media-embed/docs/features/media-embed.md
+++ b/packages/ckeditor5-media-embed/docs/features/media-embed.md
@@ -101,6 +101,16 @@ By default, the media embed feature outputs semantic `` tags f
```
+Further customization of semantic data output can be done through the {@link module:media-embed/mediaembed~MediaEmbedConfig#elementName `config.mediaEmbed.elementName`}. As an example, if `elementName` is set to `o-embed`:
+
+```html
+
+```
+
+If `elementName` is overridden to something beside the default value, existing `` elements will still be shown when for backward compatibility purposes.
+
#### Including previews in data
Optionally, by setting `mediaEmbed.previewsInData` to `true` you can configure the media embed feature to output media in the same way they look in the editor. So if the media element is "previewable", the media preview (HTML) is saved to the database:
diff --git a/packages/ckeditor5-media-embed/src/converters.js b/packages/ckeditor5-media-embed/src/converters.js
index 93ed2bcf823..ca7eb565860 100644
--- a/packages/ckeditor5-media-embed/src/converters.js
+++ b/packages/ckeditor5-media-embed/src/converters.js
@@ -28,6 +28,7 @@
* @param {module:media-embed/mediaregistry~MediaRegistry} registry The registry providing
* the media and their content.
* @param {Object} options
+ * @param {String} [options.elementName] When set, overrides the default element name for semantic media embeds.
* @param {String} [options.renderMediaPreview] When `true`, the converter will create the view in the non-semantic form.
* @param {String} [options.renderForEditingView] When `true`, the converter will create a view specific for the
* editing pipeline (e.g. including CSS classes, content placeholders).
diff --git a/packages/ckeditor5-media-embed/src/mediaembed.js b/packages/ckeditor5-media-embed/src/mediaembed.js
index 89e3a2854f1..0233f6a10cf 100644
--- a/packages/ckeditor5-media-embed/src/mediaembed.js
+++ b/packages/ckeditor5-media-embed/src/mediaembed.js
@@ -236,6 +236,34 @@ export default class MediaEmbed extends Plugin {
* @member {Array.} module:media-embed/mediaembed~MediaEmbedConfig#removeProviders
*/
+/**
+ * Overrides the element name used for "semantic" data.
+ *
+ * This is not relevant if {@link module:media-embed/mediaembed~MediaEmbedConfig#previewsInData `config.mediaEmbed.previewsInData`}
+ * is set to `true`.
+ *
+ * When unset, the feature produces tag ``:
+ *
+ *
+ *
+ * To override the element name with, for instance, the `o-embed` name:
+ *
+ * mediaEmbed: {
+ * elementName: 'o-embed'
+ * }
+ *
+ * This will produce semantic data with `` tag:
+ *
+ *
+ *
+ * @default 'oembed'
+ * @member {String} [module:media-embed/mediaembed~MediaEmbedConfig#elementName]
+ */
+
/**
* Controls the data format produced by the feature.
*
diff --git a/packages/ckeditor5-media-embed/src/mediaembedediting.js b/packages/ckeditor5-media-embed/src/mediaembedediting.js
index 7cdcd100081..c709b2957a2 100644
--- a/packages/ckeditor5-media-embed/src/mediaembedediting.js
+++ b/packages/ckeditor5-media-embed/src/mediaembedediting.js
@@ -36,6 +36,7 @@ export default class MediaEmbedEditing extends Plugin {
super( editor );
editor.config.define( 'mediaEmbed', {
+ elementName: 'oembed',
providers: [
{
name: 'dailymotion',
@@ -162,6 +163,8 @@ export default class MediaEmbedEditing extends Plugin {
const t = editor.t;
const conversion = editor.conversion;
const renderMediaPreview = editor.config.get( 'mediaEmbed.previewsInData' );
+ const elementName = editor.config.get( 'mediaEmbed.elementName' );
+
const registry = this.registry;
editor.commands.add( 'mediaEmbed', new MediaEmbedCommand( editor ) );
@@ -181,6 +184,7 @@ export default class MediaEmbedEditing extends Plugin {
const url = modelElement.getAttribute( 'url' );
return createMediaFigureElement( writer, registry, url, {
+ elementName,
renderMediaPreview: url && renderMediaPreview
} );
}
@@ -189,6 +193,7 @@ export default class MediaEmbedEditing extends Plugin {
// Model -> Data (url -> data-oembed-url)
conversion.for( 'dataDowncast' ).add(
modelToViewUrlAttributeConverter( registry, {
+ elementName,
renderMediaPreview
} ) );
@@ -198,6 +203,7 @@ export default class MediaEmbedEditing extends Plugin {
view: ( modelElement, { writer } ) => {
const url = modelElement.getAttribute( 'url' );
const figure = createMediaFigureElement( writer, registry, url, {
+ elementName,
renderForEditingView: true
} );
@@ -208,6 +214,7 @@ export default class MediaEmbedEditing extends Plugin {
// Model -> View (url -> data-oembed-url)
conversion.for( 'editingDowncast' ).add(
modelToViewUrlAttributeConverter( registry, {
+ elementName,
renderForEditingView: true
} ) );
@@ -215,12 +222,9 @@ export default class MediaEmbedEditing extends Plugin {
conversion.for( 'upcast' )
// Upcast semantic media.
.elementToElement( {
- view: {
- name: 'oembed',
- attributes: {
- url: true
- }
- },
+ view: element => [ 'oembed', elementName ].includes( element.name ) && element.getAttribute( 'url' ) ?
+ { name: true } :
+ null,
model: ( viewMedia, { writer } ) => {
const url = viewMedia.getAttribute( 'url' );
diff --git a/packages/ckeditor5-media-embed/src/mediaregistry.js b/packages/ckeditor5-media-embed/src/mediaregistry.js
index 8fd3fbfbbb5..67522d97666 100644
--- a/packages/ckeditor5-media-embed/src/mediaregistry.js
+++ b/packages/ckeditor5-media-embed/src/mediaregistry.js
@@ -88,6 +88,7 @@ export default class MediaRegistry {
* @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element.
* @param {String} url The URL to be translated into a view element.
* @param {Object} options
+ * @param {String} [options.elementName]
* @param {String} [options.renderMediaPreview]
* @param {String} [options.renderForEditingView]
* @returns {module:engine/view/element~Element}
@@ -206,6 +207,7 @@ class Media {
*
* @param {module:engine/view/downcastwriter~DowncastWriter} writer The view writer used to produce a view element.
* @param {Object} options
+ * @param {String} [options.elementName]
* @param {String} [options.renderMediaPreview]
* @param {String} [options.renderForEditingView]
* @returns {module:engine/view/element~Element}
@@ -233,7 +235,7 @@ class Media {
attributes.url = this.url;
}
- viewElement = writer.createEmptyElement( 'oembed', attributes );
+ viewElement = writer.createEmptyElement( options.elementName, attributes );
}
writer.setCustomProperty( 'media-content', true, viewElement );
diff --git a/packages/ckeditor5-media-embed/src/utils.js b/packages/ckeditor5-media-embed/src/utils.js
index f2b20b76c06..3913749aee8 100644
--- a/packages/ckeditor5-media-embed/src/utils.js
+++ b/packages/ckeditor5-media-embed/src/utils.js
@@ -68,6 +68,7 @@ export function isMediaWidget( viewElement ) {
* @param {module:media-embed/mediaregistry~MediaRegistry} registry
* @param {String} url
* @param {Object} options
+ * @param {String} [options.elementName]
* @param {String} [options.useSemanticWrapper]
* @param {String} [options.renderForEditingView]
* @returns {module:engine/view/containerelement~ContainerElement}
diff --git a/packages/ckeditor5-media-embed/tests/mediaembedediting.js b/packages/ckeditor5-media-embed/tests/mediaembedediting.js
index f4c0667ce6f..9d68e272b5d 100644
--- a/packages/ckeditor5-media-embed/tests/mediaembedediting.js
+++ b/packages/ckeditor5-media-embed/tests/mediaembedediting.js
@@ -477,6 +477,156 @@ describe( 'MediaEmbedEditing', () => {
} );
describe( 'conversion in the data pipeline', () => {
+ describe( 'elementName#o-embed', () => {
+ beforeEach( () => {
+ return createTestEditor( {
+ elementName: 'o-embed',
+ providers: providerDefinitions
+ } )
+ .then( newEditor => {
+ editor = newEditor;
+ model = editor.model;
+ doc = model.document;
+ view = editor.editing.view;
+ } );
+ } );
+
+ describe( 'model to view', () => {
+ it( 'should convert', () => {
+ setModelData( model, ' ' );
+
+ expect( editor.getData() ).to.equal(
+ '' );
+ } );
+
+ it( 'should convert (no url)', () => {
+ setModelData( model, ' ' );
+
+ expect( editor.getData() ).to.equal(
+ '' );
+ } );
+
+ it( 'should convert (preview-less media)', () => {
+ setModelData( model, ' ' );
+
+ expect( editor.getData() ).to.equal(
+ '' );
+ } );
+ } );
+
+ describe( 'view to model', () => {
+ it( 'should convert media figure', () => {
+ editor.setData( '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( ' ' );
+ } );
+
+ it( 'should not convert if there is no media class', () => {
+ editor.setData( '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( '' );
+ } );
+
+ it( 'should not convert if there is no o-embed wrapper inside #1', () => {
+ editor.setData( '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( '' );
+ } );
+
+ it( 'should not convert if there is no o-embed wrapper inside #2', () => {
+ editor.setData( '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( '' );
+ } );
+
+ it( 'should not convert when the wrapper has no data-o-embed-url attribute', () => {
+ editor.setData( '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( '' );
+ } );
+
+ it( 'should not convert in the wrong context', () => {
+ model.schema.register( 'blockquote', { inheritAllFrom: '$block' } );
+ model.schema.addChildCheck( ( ctx, childDef ) => {
+ if ( ctx.endsWith( '$root' ) && childDef.name == 'media' ) {
+ return false;
+ }
+ } );
+
+ editor.conversion.elementToElement( { model: 'blockquote', view: 'blockquote' } );
+
+ editor.setData(
+ '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( '' );
+ } );
+
+ it( 'should not convert if the o-embed wrapper is already consumed', () => {
+ editor.data.upcastDispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
+ const img = data.viewItem.getChild( 0 );
+ conversionApi.consumable.consume( img, { name: true } );
+ }, { priority: 'high' } );
+
+ editor.setData( '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( '' );
+ } );
+
+ it( 'should not convert if the figure is already consumed', () => {
+ editor.data.upcastDispatcher.on( 'element:figure', ( evt, data, conversionApi ) => {
+ conversionApi.consumable.consume( data.viewItem, { name: true, class: 'image' } );
+ }, { priority: 'high' } );
+
+ editor.setData( '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( '' );
+ } );
+
+ it( 'should discard the contents of the media', () => {
+ editor.setData( '' );
+
+ expect( getModelData( model, { withoutSelection: true } ) )
+ .to.equal( ' ' );
+ } );
+
+ it( 'should not convert unknown media', () => {
+ return createTestEditor( {
+ providers: [
+ testProviders.A
+ ]
+ } )
+ .then( newEditor => {
+ newEditor.setData(
+ '' +
+ '' );
+
+ expect( getModelData( newEditor.model, { withoutSelection: true } ) )
+ .to.equal( ' ' );
+
+ return newEditor.destroy();
+ } );
+ } );
+ } );
+ } );
+
describe( 'previewsInData=false', () => {
beforeEach( () => {
return createTestEditor( {
diff --git a/packages/ckeditor5-page-break/src/pagebreakediting.js b/packages/ckeditor5-page-break/src/pagebreakediting.js
index c88f74e0e94..3b8d4c8497e 100644
--- a/packages/ckeditor5-page-break/src/pagebreakediting.js
+++ b/packages/ckeditor5-page-break/src/pagebreakediting.js
@@ -99,14 +99,8 @@ export default class PageBreakEditing extends Plugin {
if ( element.childCount == 1 ) {
const viewSpan = element.getChild( 0 );
- // The child must be the "span" element that is not displayed and has a space inside.
- if ( !viewSpan.is( 'element', 'span' ) || viewSpan.getStyle( 'display' ) != 'none' || viewSpan.childCount != 1 ) {
- return;
- }
-
- const text = viewSpan.getChild( 0 );
-
- if ( !text.is( '$text' ) || text.data !== ' ' ) {
+ // The child must be the "span" element that is not displayed.
+ if ( !viewSpan.is( 'element', 'span' ) || viewSpan.getStyle( 'display' ) != 'none' ) {
return;
}
} else if ( element.childCount > 1 ) {
diff --git a/packages/ckeditor5-page-break/tests/pagebreakediting.js b/packages/ckeditor5-page-break/tests/pagebreakediting.js
index 65e5c8e943c..fce518e3106 100644
--- a/packages/ckeditor5-page-break/tests/pagebreakediting.js
+++ b/packages/ckeditor5-page-break/tests/pagebreakediting.js
@@ -172,34 +172,21 @@ describe( 'PageBreakEditing', () => {
.to.equal( '' );
} );
- it( 'should not convert if inner span has wrong styles', () => {
+ it( 'should convert if inner span is empty', () => {
editor.setData(
'' +
- ' ' +
+ '' +
''
);
expect( getModelData( model, { withoutSelection: true } ) )
- .to.equal( '' );
- } );
-
- it( 'should not convert if inner span has any children', () => {
- editor.setData(
- '' +
- ''
- );
-
- expect( getModelData( model, { withoutSelection: true } ) )
- .to.equal( '' );
+ .to.equal( ' ' +
- ' ' );
} );
- it( 'should not convert if inner span has text', () => {
+ it( 'should not convert if inner span has wrong styles', () => {
editor.setData(
'' +
- ' ' +
''
);
@@ -236,26 +223,6 @@ describe( 'PageBreakEditing', () => {
expect( getModelData( model, { withoutSelection: true } ) )
.to.equal( ' ' +
+ '<$text foo="true">Foo$text> ' );
} );
-
- it( 'should not convert if inner span has no children', () => {
- editor.setData( '' );
-
- expect( getModelData( model, { withoutSelection: true } ) )
- .to.equal( '' );
- } );
-
- it( 'should not convert if inner span has other element as a child', () => {
- editor.setData(
- '' +
- ''
- );
-
- expect( getModelData( model, { withoutSelection: true } ) )
- .to.equal( '' );
- } );
} );
} );
diff --git a/packages/ckeditor5-remove-format/docs/features/remove-format.md b/packages/ckeditor5-remove-format/docs/features/remove-format.md
index bfcb906c027..20c10df8fc8 100644
--- a/packages/ckeditor5-remove-format/docs/features/remove-format.md
+++ b/packages/ckeditor5-remove-format/docs/features/remove-format.md
@@ -19,7 +19,7 @@ Select the content you want to clean up and press the "Remove Format" button in
## Related features
There are more CKEditor 5 features that can help you format your content:
-* {@link features/basic-styles Basic font styles} – The essentials, like **bold**, *italic* and others.
+* {@link features/basic-styles Basic text styles} – The essentials, like **bold**, *italic* and others.
* {@link features/font Font styles} – Easily and efficiently control the font {@link features/font#configuring-the-font-family-feature family}, {@link features/font#configuring-the-font-size-feature size}, {@link features/font#configuring-the-font-color-and-font-background-color-features text or background color}.
* {@link features/text-alignment Text alignment} – Align your content left, align it right, center it or justify.
diff --git a/packages/ckeditor5-restricted-editing/docs/_snippets/features/restricted-editing.js b/packages/ckeditor5-restricted-editing/docs/_snippets/features/restricted-editing.js
index 7dee196fcba..46ec6bae7d4 100644
--- a/packages/ckeditor5-restricted-editing/docs/_snippets/features/restricted-editing.js
+++ b/packages/ckeditor5-restricted-editing/docs/_snippets/features/restricted-editing.js
@@ -39,9 +39,9 @@ async function startStandardEditingMode() {
await reloadEditor( {
removePlugins: [ 'RestrictedEditingMode' ],
toolbar: [
- 'heading', '|', 'bold', 'italic', 'link', '|',
+ 'restrictedEditingException', '|', 'heading', '|', 'bold', 'italic', 'link', '|',
'bulletedList', 'numberedList', 'blockQuote', 'insertTable', '|',
- 'restrictedEditingException', '|', 'undo', 'redo'
+ 'undo', 'redo'
],
image: {
toolbar: [ 'imageStyle:full', 'imageStyle:side', '|', 'imageTextAlternative' ]
@@ -59,7 +59,7 @@ async function startStandardEditingMode() {
async function startRestrictedEditingMode() {
await reloadEditor( {
removePlugins: [ 'StandardEditingMode' ],
- toolbar: [ 'bold', 'italic', 'link', '|', 'restrictedEditing', '|', 'undo', 'redo' ]
+ toolbar: [ 'restrictedEditing', '|', 'bold', 'italic', 'link', '|', 'undo', 'redo' ]
} );
}
@@ -76,6 +76,9 @@ async function reloadEditor( config ) {
item => item.label && [ 'Enable editing', 'Disable editing' ].includes( item.label )
),
text: 'Click to add or remove editable regions.',
- editor: window.editor
+ editor: window.editor,
+ tippyOptions: {
+ placement: 'bottom-start'
+ }
} );
}
diff --git a/packages/ckeditor5-special-characters/lang/translations/cs.po b/packages/ckeditor5-special-characters/lang/translations/cs.po
new file mode 100644
index 00000000000..301d2b7bbf7
--- /dev/null
+++ b/packages/ckeditor5-special-characters/lang/translations/cs.po
@@ -0,0 +1,1037 @@
+# Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+#
+# !!! IMPORTANT !!!
+#
+# Before you edit this file, please keep in mind that contributing to the project
+# translations is possible ONLY via the Transifex online service.
+#
+# To submit your translations, visit https://www.transifex.com/ckeditor/ckeditor5.
+#
+# To learn more, check out the official contributor's guide:
+# https://ckeditor.com/docs/ckeditor5/latest/framework/guides/contributing/contributing.html
+#
+msgid ""
+msgstr ""
+"Language-Team: Czech (https://www.transifex.com/ckeditor/teams/11143/cs/)\n"
+"Language: cs\n"
+"Plural-Forms: nplurals=4; plural=(n == 1 && n % 1 == 0) ? 0 : (n >= 2 && n <= 4 && n % 1 == 0) ? 1: (n % 1 != 0 ) ? 2 : 3;\n"
+
+msgctxt "Name of the special characters plugins, visible in a dropdown and as a button tooltip."
+msgid "Special characters"
+msgstr "Speciální znaky"
+
+msgctxt "A label for the \"leftwards double arrow\" symbol."
+msgid "leftwards double arrow"
+msgstr "dvojitá šipka doleva"
+
+msgctxt "A label for the \"rightwards double arrow\" symbol."
+msgid "rightwards double arrow"
+msgstr "dvojitá šipka doprava"
+
+msgctxt "A label for the \"upwards double arrow\" symbol."
+msgid "upwards double arrow"
+msgstr "dvojitá šipka nahoru"
+
+msgctxt "A label for the \"downwards double arrow\" symbol."
+msgid "downwards double arrow"
+msgstr "dvojitá šipka dolů"
+
+msgctxt "A label for the \"leftwards dashed arrow\" symbol."
+msgid "leftwards dashed arrow"
+msgstr "přerušovaná šipka doleva"
+
+msgctxt "A label for the \"rightwards dashed arrow\" symbol."
+msgid "rightwards dashed arrow"
+msgstr "čárkovaná šipka doprava"
+
+msgctxt "A label for the \"upwards dashed arrow\" symbol."
+msgid "upwards dashed arrow"
+msgstr "čárkovaná šipka nahoru"
+
+msgctxt "A label for the \"downwards dashed arrow\" symbol."
+msgid "downwards dashed arrow"
+msgstr "přerušovaná šipka dolů"
+
+msgctxt "A label for the \"leftwards arrow to bar\" symbol."
+msgid "leftwards arrow to bar"
+msgstr "šipka doleva do svislé čáry"
+
+msgctxt "A label for the \"rightwards arrow to bar\" symbol."
+msgid "rightwards arrow to bar"
+msgstr "šipka doprava do svislé čáry"
+
+msgctxt "A label for the \"upwards arrow to bar\" symbol."
+msgid "upwards arrow to bar"
+msgstr "šipka nahoru do svislé čáry"
+
+msgctxt "A label for the \"downwards arrow to bar\" symbol."
+msgid "downwards arrow to bar"
+msgstr "šipka dolů do svislé čáry"
+
+msgctxt "A label for the \"up down arrow with base\" symbol."
+msgid "up down arrow with base"
+msgstr "Šipka nahoru-dolů od základny"
+
+msgctxt "A label for the \"back with leftwards arrow above\" symbol."
+msgid "back with leftwards arrow above"
+msgstr "šipka zpět"
+
+msgctxt "A label for the \"end with leftwards arrow above\" symbol."
+msgid "end with leftwards arrow above"
+msgstr "šipka konec"
+
+msgctxt "A label for the \"on with exclamation mark with left right arrow above\" symbol."
+msgid "on with exclamation mark with left right arrow above"
+msgstr "ON s vykřičníkem se šipkou doleva doprava nahoru"
+
+msgctxt "A label for the \"soon with rightwards arrow above\" symbol."
+msgid "soon with rightwards arrow above"
+msgstr "brzy se šipkou doprava nahoru"
+
+msgctxt "A label for the \"top with upwards arrow above\" symbol."
+msgid "top with upwards arrow above"
+msgstr "TOP se šipkou nahoru"
+
+msgctxt "A label for the \"dollar sign\" symbol."
+msgid "Dollar sign"
+msgstr "Znak Dolar"
+
+msgctxt "A label for the \"euro sign\" symbol."
+msgid "Euro sign"
+msgstr "Znak Euro"
+
+msgctxt "A label for the \"yen sign\" symbol."
+msgid "Yen sign"
+msgstr "Znak Jen"
+
+msgctxt "A label for the \"pound sign\" symbol."
+msgid "Pound sign"
+msgstr "Znak Libra"
+
+msgctxt "A label for the \"cent sign\" symbol."
+msgid "Cent sign"
+msgstr "Znak cent"
+
+msgctxt "A label for the \"euro-currency sign\" symbol."
+msgid "Euro-currency sign"
+msgstr "Mena Euro"
+
+msgctxt "A label for the \"colon sign\" symbol."
+msgid "Colon sign"
+msgstr "dvojtečka"
+
+msgctxt "A label for the \"cruzeiro sign\" symbol."
+msgid "Cruzeiro sign"
+msgstr "Měna Cruzeiro"
+
+msgctxt "A label for the \"french franc sign\" symbol."
+msgid "French franc sign"
+msgstr "Měna Francouzský Frank"
+
+msgctxt "A label for the \"lira sign\" symbol."
+msgid "Lira sign"
+msgstr "Měna Lira"
+
+msgctxt "A label for the \"currency sign\" symbol."
+msgid "Currency sign"
+msgstr "Znak měny"
+
+msgctxt "A label for the \"bitcoin sign\" symbol."
+msgid "Bitcoin sign"
+msgstr "Měna Bitcoin"
+
+msgctxt "A label for the \"mill sign\" symbol."
+msgid "Mill sign"
+msgstr "Znak Mill"
+
+msgctxt "A label for the \"naira sign\" symbol."
+msgid "Naira sign"
+msgstr "Znak Naira"
+
+msgctxt "A label for the \"peseta sign\" symbol."
+msgid "Peseta sign"
+msgstr "Znak Peseta"
+
+msgctxt "A label for the \"rupee sign\" symbol."
+msgid "Rupee sign"
+msgstr "Znak Rupee"
+
+msgctxt "A label for the \"won sign\" symbol."
+msgid "Won sign"
+msgstr "Znak Won"
+
+msgctxt "A label for the \"new sheqel sign\" symbol."
+msgid "New sheqel sign"
+msgstr "Nový znak šekel"
+
+msgctxt "A label for the \"dong sign\" symbol."
+msgid "Dong sign"
+msgstr "Znak Dong"
+
+msgctxt "A label for the \"kip sign\" symbol."
+msgid "Kip sign"
+msgstr "Znak Kip"
+
+msgctxt "A label for the \"tugrik sign\" symbol."
+msgid "Tugrik sign"
+msgstr "Znak Tugrik"
+
+msgctxt "A label for the \"drachma sign\" symbol."
+msgid "Drachma sign"
+msgstr "Znak Drachma"
+
+msgctxt "A label for the \"german penny sign\" symbol."
+msgid "German penny sign"
+msgstr "Německá penny"
+
+msgctxt "A label for the \"peso sign\" symbol."
+msgid "Peso sign"
+msgstr "Znak Peso"
+
+msgctxt "A label for the \"guarani sign\" symbol."
+msgid "Guarani sign"
+msgstr "Znak Guarani"
+
+msgctxt "A label for the \"austral sign\" symbol."
+msgid "Austral sign"
+msgstr "Znak Austral"
+
+msgctxt "A label for the \"hryvnia sign\" symbol."
+msgid "Hryvnia sign"
+msgstr "Znak Hryvnia"
+
+msgctxt "A label for the \"cedi sign\" symbol."
+msgid "Cedi sign"
+msgstr "Znak Cedi"
+
+msgctxt "A label for the \"livre tournois sign\" symbol."
+msgid "Livre tournois sign"
+msgstr "Znak Livre tournois"
+
+msgctxt "A label for the \"spesmilo sign\" symbol."
+msgid "Spesmilo sign"
+msgstr "Znak Spesmilo"
+
+msgctxt "A label for the \"tenge sign\" symbol."
+msgid "Tenge sign"
+msgstr "Znak Tenge"
+
+msgctxt "A label for the \"indian rupee sign\" symbol."
+msgid "Indian rupee sign"
+msgstr "Znak Indická rupia"
+
+msgctxt "A label for the \"turkish lira sign\" symbol."
+msgid "Turkish lira sign"
+msgstr "Znak Turecká líra"
+
+msgctxt "A label for the \"nordic mark sign\" symbol."
+msgid "Nordic mark sign"
+msgstr "Znak Nórska marka"
+
+msgctxt "A label for the \"manat sign\" symbol."
+msgid "Manat sign"
+msgstr "Znak Manat"
+
+msgctxt "A label for the \"ruble sign\" symbol."
+msgid "Ruble sign"
+msgstr "Znak Ruble"
+
+msgctxt "A label for the \"latin capital letter a with macron\" symbol."
+msgid "Latin capital letter a with macron"
+msgstr "Latinské velké písmeno a s čárou"
+
+msgctxt "A label for the \"latin small letter a with macron\" symbol."
+msgid "Latin small letter a with macron"
+msgstr "Latinské malé písmeno a s čárou"
+
+msgctxt "A label for the \"latin capital letter a with breve\" symbol."
+msgid "Latin capital letter a with breve"
+msgstr "Latinské velké písmeno a s háčkem"
+
+msgctxt "A label for the \"latin small letter a with breve\" symbol."
+msgid "Latin small letter a with breve"
+msgstr "Latinské malé písmeno a s háčkem"
+
+msgctxt "A label for the \"latin capital letter a with ogonek\" symbol."
+msgid "Latin capital letter a with ogonek"
+msgstr "Latinské velké písmeno a s háčkem"
+
+msgctxt "A label for the \"latin small letter a with ogonek\" symbol."
+msgid "Latin small letter a with ogonek"
+msgstr "Latinské malé písmeno a s háčkem"
+
+msgctxt "A label for the \"latin capital letter c with acute\" symbol."
+msgid "Latin capital letter c with acute"
+msgstr "Latinské velké písmeno c s čárkou"
+
+msgctxt "A label for the \"latin small letter c with acute\" symbol."
+msgid "Latin small letter c with acute"
+msgstr "Latinské malé písmeno c s čárkou"
+
+msgctxt "A label for the \"latin capital letter c with circumflex\" symbol."
+msgid "Latin capital letter c with circumflex"
+msgstr "Latinské velké písmeno c s obráceným háčkem"
+
+msgctxt "A label for the \"latin small letter c with circumflex\" symbol."
+msgid "Latin small letter c with circumflex"
+msgstr "Latinské malé písmeno c s obráceným háčkem"
+
+msgctxt "A label for the \"latin capital letter c with dot above\" symbol."
+msgid "Latin capital letter c with dot above"
+msgstr "Latinské velké písmeno c s tečkou nad znakem"
+
+msgctxt "A label for the \"latin small letter c with dot above\" symbol."
+msgid "Latin small letter c with dot above"
+msgstr "Latinské malé písmeno c s tečkou nad znakem"
+
+msgctxt "A label for the \"latin capital letter c with caron\" symbol."
+msgid "Latin capital letter c with caron"
+msgstr "Latinské veľké písmeno c s mäkčeňom"
+
+msgctxt "A label for the \"latin small letter c with caron\" symbol."
+msgid "Latin small letter c with caron"
+msgstr "Latinské malé písmeno c s háčkem"
+
+msgctxt "A label for the \"latin capital letter d with caron\" symbol."
+msgid "Latin capital letter d with caron"
+msgstr "Latinské velké písmeno d s háčkem"
+
+msgctxt "A label for the \"latin small letter d with caron\" symbol."
+msgid "Latin small letter d with caron"
+msgstr "Latinské malé písmeno d s háčkem"
+
+msgctxt "A label for the \"latin capital letter d with stroke\" symbol."
+msgid "Latin capital letter d with stroke"
+msgstr "Latinské velké písmeno d s přeškrtnutím"
+
+msgctxt "A label for the \"latin small letter d with stroke\" symbol."
+msgid "Latin small letter d with stroke"
+msgstr "Latinské malé písmeno d s přeškrtnutím"
+
+msgctxt "A label for the \"latin capital letter e with macron\" symbol."
+msgid "Latin capital letter e with macron"
+msgstr "Latinské velké písmeno e s čárou"
+
+msgctxt "A label for the \"latin small letter e with macron\" symbol."
+msgid "Latin small letter e with macron"
+msgstr "Latinské malé písmeno e s čárou"
+
+msgctxt "A label for the \"latin capital letter e with breve\" symbol."
+msgid "Latin capital letter e with breve"
+msgstr "Latinské velké písmeno e s háčkem"
+
+msgctxt "A label for the \"latin small letter e with breve\" symbol."
+msgid "Latin small letter e with breve"
+msgstr "Latinské malé písmeno e s háčkem"
+
+msgctxt "A label for the \"latin capital letter e with dot above\" symbol."
+msgid "Latin capital letter e with dot above"
+msgstr "Latinské velké písmeno e s tečkou nad znakem"
+
+msgctxt "A label for the \"latin small letter e with dot above\" symbol."
+msgid "Latin small letter e with dot above"
+msgstr "Latinské malé písmeno e s tečkou nad znakem"
+
+msgctxt "A label for the \"latin capital letter e with ogonek\" symbol."
+msgid "Latin capital letter e with ogonek"
+msgstr "Latinské velké písmeno e s háčkem"
+
+msgctxt "A label for the \"latin small letter e with ogonek\" symbol."
+msgid "Latin small letter e with ogonek"
+msgstr "Latinské malé písmeno e s háčkem"
+
+msgctxt "A label for the \"latin capital letter e with caron\" symbol."
+msgid "Latin capital letter e with caron"
+msgstr "Latinské velké písmeno e s háčkem"
+
+msgctxt "A label for the \"latin small letter e with caron\" symbol."
+msgid "Latin small letter e with caron"
+msgstr "Latinské malé písmeno e s háčkem"
+
+msgctxt "A label for the \"latin capital letter g with circumflex\" symbol."
+msgid "Latin capital letter g with circumflex"
+msgstr "Latinské velké písmeno g s obráceným háčkem"
+
+msgctxt "A label for the \"latin small letter g with circumflex\" symbol."
+msgid "Latin small letter g with circumflex"
+msgstr "Latinské malé písmeno g s obráceným háčkem"
+
+msgctxt "A label for the \"latin capital letter g with breve\" symbol."
+msgid "Latin capital letter g with breve"
+msgstr "Latinské velké písmeno g s háčkem"
+
+msgctxt "A label for the \"latin small letter g with breve\" symbol."
+msgid "Latin small letter g with breve"
+msgstr "Latinské malé písmeno g s háčkem"
+
+msgctxt "A label for the \"latin capital letter g with dot above\" symbol."
+msgid "Latin capital letter g with dot above"
+msgstr "Latinské velké písmeno g s tečkou nad znakem"
+
+msgctxt "A label for the \"latin small letter g with dot above\" symbol."
+msgid "Latin small letter g with dot above"
+msgstr "Latinské malé písmeno g s tečkou nad znakem"
+
+msgctxt "A label for the \"latin capital letter g with cedilla\" symbol."
+msgid "Latin capital letter g with cedilla"
+msgstr "Latinské velké písmeno g s háčkem"
+
+msgctxt "A label for the \"latin small letter g with cedilla\" symbol."
+msgid "Latin small letter g with cedilla"
+msgstr "Latinské malé písmeno g s háčkem"
+
+msgctxt "A label for the \"latin capital letter h with circumflex\" symbol."
+msgid "Latin capital letter h with circumflex"
+msgstr "Latinské velké písmeno h s obráceným háčkem"
+
+msgctxt "A label for the \"latin small letter h with circumflex\" symbol."
+msgid "Latin small letter h with circumflex"
+msgstr "Latinské malé písmeno h s obráceným háčkem"
+
+msgctxt "A label for the \"latin capital letter h with stroke\" symbol."
+msgid "Latin capital letter h with stroke"
+msgstr "Latinské velké písmeno h s přeškrtnutím"
+
+msgctxt "A label for the \"latin small letter h with stroke\" symbol."
+msgid "Latin small letter h with stroke"
+msgstr "Latinské malé písmeno h s přeškrtnutím"
+
+msgctxt "A label for the \"latin capital letter i with tilde\" symbol."
+msgid "Latin capital letter i with tilde"
+msgstr "Latinské velké písmeno i s vlnovkou"
+
+msgctxt "A label for the \"latin small letter i with tilde\" symbol."
+msgid "Latin small letter i with tilde"
+msgstr "Latinské malé písmeno i s vlnovkou"
+
+msgctxt "A label for the \"latin capital letter i with macron\" symbol."
+msgid "Latin capital letter i with macron"
+msgstr "Latinské velké písmeno i s čárou"
+
+msgctxt "A label for the \"latin small letter i with macron\" symbol."
+msgid "Latin small letter i with macron"
+msgstr "Latinské malé písmeno i s čárou"
+
+msgctxt "A label for the \"latin capital letter i with breve\" symbol."
+msgid "Latin capital letter i with breve"
+msgstr "Latinské velké písmeno i s háčkem"
+
+msgctxt "A label for the \"latin small letter i with breve\" symbol."
+msgid "Latin small letter i with breve"
+msgstr "Latinské malé písmeno i s háčkem"
+
+msgctxt "A label for the \"latin capital letter i with ogonek\" symbol."
+msgid "Latin capital letter i with ogonek"
+msgstr "Latinské velké písmeno i s háčkem"
+
+msgctxt "A label for the \"latin small letter i with ogonek\" symbol."
+msgid "Latin small letter i with ogonek"
+msgstr "Latinské malé písmeno i s háčkem"
+
+msgctxt "A label for the \"latin capital letter i with dot above\" symbol."
+msgid "Latin capital letter i with dot above"
+msgstr "Latinské velké písmeno i s tečkou nad znakem"
+
+msgctxt "A label for the \"latin small letter dotless i\" symbol."
+msgid "Latin small letter dotless i"
+msgstr "Latinské malé písmeno i bez tečky"
+
+msgctxt "A label for the \"latin capital ligature ij\" symbol."
+msgid "Latin capital ligature ij"
+msgstr "Latinský velký znak ligatury ij"
+
+msgctxt "A label for the \"latin small ligature ij\" symbol."
+msgid "Latin small ligature ij"
+msgstr "Latinský malý znak ligatury ij"
+
+msgctxt "A label for the \"latin capital letter j with circumflex\" symbol."
+msgid "Latin capital letter j with circumflex"
+msgstr "Latinské velké písmeno j s obráceným háčkem"
+
+msgctxt "A label for the \"latin small letter j with circumflex\" symbol."
+msgid "Latin small letter j with circumflex"
+msgstr "Latinské malé písmeno j s obráceným háčkem"
+
+msgctxt "A label for the \"latin capital letter k with cedilla\" symbol."
+msgid "Latin capital letter k with cedilla"
+msgstr "Latinské velké písmeno k s háčkem"
+
+msgctxt "A label for the \"latin small letter k with cedilla\" symbol."
+msgid "Latin small letter k with cedilla"
+msgstr "Latinské malé písmeno k s háčkem"
+
+msgctxt "A label for the \"latin small letter kra\" symbol."
+msgid "Latin small letter kra"
+msgstr "Latinský malý znak Kra"
+
+msgctxt "A label for the \"latin capital letter l with acute\" symbol."
+msgid "Latin capital letter l with acute"
+msgstr "Latinské velké písmeno l s čárkou"
+
+msgctxt "A label for the \"latin small letter l with acute\" symbol."
+msgid "Latin small letter l with acute"
+msgstr "Latinské malé písmeno l s čárkou"
+
+msgctxt "A label for the \"latin capital letter l with cedilla\" symbol."
+msgid "Latin capital letter l with cedilla"
+msgstr "Latinské velké písmeno l s háčkem"
+
+msgctxt "A label for the \"latin small letter l with cedilla\" symbol."
+msgid "Latin small letter l with cedilla"
+msgstr "Latinské malé písmeno l s háčkem"
+
+msgctxt "A label for the \"latin capital letter l with caron\" symbol."
+msgid "Latin capital letter l with caron"
+msgstr "Latinské velké písmeno l s háčkem"
+
+msgctxt "A label for the \"latin small letter l with caron\" symbol."
+msgid "Latin small letter l with caron"
+msgstr "Latinské malé písmeno l s háčkem"
+
+msgctxt "A label for the \"latin capital letter l with middle dot\" symbol."
+msgid "Latin capital letter l with middle dot"
+msgstr "Latinské velké písmeno l s tečkou uprostřed"
+
+msgctxt "A label for the \"latin small letter l with middle dot\" symbol."
+msgid "Latin small letter l with middle dot"
+msgstr "Latinské malé písmeno l s tečkou uprostřed"
+
+msgctxt "A label for the \"latin capital letter l with stroke\" symbol."
+msgid "Latin capital letter l with stroke"
+msgstr "Latinské velké písmeno l s přeškrtnutím"
+
+msgctxt "A label for the \"latin small letter l with stroke\" symbol."
+msgid "Latin small letter l with stroke"
+msgstr "Latinské malé písmeno l s přeškrtnutím"
+
+msgctxt "A label for the \"latin capital letter n with acute\" symbol."
+msgid "Latin capital letter n with acute"
+msgstr "Latinské velké písmeno n s čárkou"
+
+msgctxt "A label for the \"latin small letter n with acute\" symbol."
+msgid "Latin small letter n with acute"
+msgstr "Latinské malé písmeno n s čárkou"
+
+msgctxt "A label for the \"latin capital letter n with cedilla\" symbol."
+msgid "Latin capital letter n with cedilla"
+msgstr "Latinské velké písmeno n s háčkem"
+
+msgctxt "A label for the \"latin small letter n with cedilla\" symbol."
+msgid "Latin small letter n with cedilla"
+msgstr "Latinské malé písmeno n s háčkem"
+
+msgctxt "A label for the \"latin capital letter n with caron\" symbol."
+msgid "Latin capital letter n with caron"
+msgstr "Latinské velké písmeno n s háčkem"
+
+msgctxt "A label for the \"latin small letter n with caron\" symbol."
+msgid "Latin small letter n with caron"
+msgstr "Latinské malé písmeno n s háčkem"
+
+msgctxt "A label for the \"latin small letter n preceded by apostrophe\" symbol."
+msgid "Latin small letter n preceded by apostrophe"
+msgstr "Latinské malé písmeno n s apostrofem"
+
+msgctxt "A label for the \"latin capital letter eng\" symbol."
+msgid "Latin capital letter eng"
+msgstr "Latinské velké písmeno Eng"
+
+msgctxt "A label for the \"latin small letter eng\" symbol."
+msgid "Latin small letter eng"
+msgstr "Latinské malé písmeno Eng"
+
+msgctxt "A label for the \"latin capital letter o with macron\" symbol."
+msgid "Latin capital letter o with macron"
+msgstr "Latinské velké písmeno o s čárou"
+
+msgctxt "A label for the \"latin small letter o with macron\" symbol."
+msgid "Latin small letter o with macron"
+msgstr "Latinské malé písmeno o s čárou"
+
+msgctxt "A label for the \"latin capital letter o with breve\" symbol."
+msgid "Latin capital letter o with breve"
+msgstr "Latinské velké písmeno o s háčkem"
+
+msgctxt "A label for the \"latin small letter o with breve\" symbol."
+msgid "Latin small letter o with breve"
+msgstr "Latinské malé písmeno o s háčkem"
+
+msgctxt "A label for the \"latin capital letter o with double acute\" symbol."
+msgid "Latin capital letter o with double acute"
+msgstr "Latinské velké písmeno o s čárkou"
+
+msgctxt "A label for the \"latin small letter o with double acute\" symbol."
+msgid "Latin small letter o with double acute"
+msgstr "Latinské malé písmeno o s čárkou"
+
+msgctxt "A label for the \"latin capital ligature oe\" symbol."
+msgid "Latin capital ligature oe"
+msgstr "Latinský velký znak ligatury oe"
+
+msgctxt "A label for the \"latin small ligature oe\" symbol."
+msgid "Latin small ligature oe"
+msgstr "Latinský malý znak ligatury oe"
+
+msgctxt "A label for the \"latin capital letter r with acute\" symbol."
+msgid "Latin capital letter r with acute"
+msgstr "Latinské velké písmeno r s čárkou"
+
+msgctxt "A label for the \"latin small letter r with acute\" symbol."
+msgid "Latin small letter r with acute"
+msgstr "Latinské malé písmeno r s čárkou"
+
+msgctxt "A label for the \"latin capital letter r with cedilla\" symbol."
+msgid "Latin capital letter r with cedilla"
+msgstr "Latinské velké písmeno r s háčkem"
+
+msgctxt "A label for the \"latin small letter r with cedilla\" symbol."
+msgid "Latin small letter r with cedilla"
+msgstr "Latinské malé písmeno r s háčkem"
+
+msgctxt "A label for the \"latin capital letter r with caron\" symbol."
+msgid "Latin capital letter r with caron"
+msgstr "Latinské velké písmeno r s háčkem"
+
+msgctxt "A label for the \"latin small letter r with caron\" symbol."
+msgid "Latin small letter r with caron"
+msgstr "Latinské malé písmeno r s háčkem"
+
+msgctxt "A label for the \"latin capital letter s with acute\" symbol."
+msgid "Latin capital letter s with acute"
+msgstr "Latinské velké písmeno s s čárkou"
+
+msgctxt "A label for the \"latin small letter s with acute\" symbol."
+msgid "Latin small letter s with acute"
+msgstr "Latinské malé písmeno s s čárkou"
+
+msgctxt "A label for the \"latin capital letter s with circumflex\" symbol."
+msgid "Latin capital letter s with circumflex"
+msgstr "Latinské velké písmeno s s obráceným háčkem"
+
+msgctxt "A label for the \"latin small letter s with circumflex\" symbol."
+msgid "Latin small letter s with circumflex"
+msgstr "Latinské malé písmeno s s obráceným háčkem"
+
+msgctxt "A label for the \"latin capital letter s with cedilla\" symbol."
+msgid "Latin capital letter s with cedilla"
+msgstr "Latinské velké písmeno s s háčkem"
+
+msgctxt "A label for the \"latin small letter s with cedilla\" symbol."
+msgid "Latin small letter s with cedilla"
+msgstr "Latinské malé písmeno s s háčkem"
+
+msgctxt "A label for the \"latin capital letter s with caron\" symbol."
+msgid "Latin capital letter s with caron"
+msgstr "Latinské velké písmeno s s háčkem"
+
+msgctxt "A label for the \"latin small letter s with caron\" symbol."
+msgid "Latin small letter s with caron"
+msgstr "Latinské malé písmeno s s háčkem"
+
+msgctxt "A label for the \"latin capital letter t with cedilla\" symbol."
+msgid "Latin capital letter t with cedilla"
+msgstr "Latinské velké písmeno t s háčkem"
+
+msgctxt "A label for the \"latin small letter t with cedilla\" symbol."
+msgid "Latin small letter t with cedilla"
+msgstr "Latinské malé písmeno t s háčkem"
+
+msgctxt "A label for the \"latin capital letter t with caron\" symbol."
+msgid "Latin capital letter t with caron"
+msgstr "Latinské velké písmeno t s háčkem"
+
+msgctxt "A label for the \"latin small letter t with caron\" symbol."
+msgid "Latin small letter t with caron"
+msgstr "Latinské malé písmeno t s háčkem"
+
+msgctxt "A label for the \"latin capital letter t with stroke\" symbol."
+msgid "Latin capital letter t with stroke"
+msgstr "Latinské velké písmeno t s přeškrtnutím"
+
+msgctxt "A label for the \"latin small letter t with stroke\" symbol."
+msgid "Latin small letter t with stroke"
+msgstr "Latinské malé písmeno t s přeškrtnutím"
+
+msgctxt "A label for the \"latin capital letter u with tilde\" symbol."
+msgid "Latin capital letter u with tilde"
+msgstr "Latinské velké písmeno u s vlnovkou"
+
+msgctxt "A label for the \"latin small letter u with tilde\" symbol."
+msgid "Latin small letter u with tilde"
+msgstr "Latinské malé písmeno u s vlnovkou"
+
+msgctxt "A label for the \"latin capital letter u with macron\" symbol."
+msgid "Latin capital letter u with macron"
+msgstr "Latinské velké písmeno u s čárou"
+
+msgctxt "A label for the \"latin small letter u with macron\" symbol."
+msgid "Latin small letter u with macron"
+msgstr "Latinské malé písmeno o s čárou"
+
+msgctxt "A label for the \"latin capital letter u with breve\" symbol."
+msgid "Latin capital letter u with breve"
+msgstr "Latinské velké písmeno u s háčkem"
+
+msgctxt "A label for the \"latin small letter u with breve\" symbol."
+msgid "Latin small letter u with breve"
+msgstr "Latinské malé písmeno u s háčkem"
+
+msgctxt "A label for the \"latin capital letter u with ring above\" symbol."
+msgid "Latin capital letter u with ring above"
+msgstr "Latinské velké písmeno u s kroužkem nad znakem"
+
+msgctxt "A label for the \"latin small letter u with ring above\" symbol."
+msgid "Latin small letter u with ring above"
+msgstr "Latinské malé písmeno u s kroužkem nad znakem"
+
+msgctxt "A label for the \"latin capital letter u with double acute\" symbol."
+msgid "Latin capital letter u with double acute"
+msgstr "Latinské velké písmeno u s dvojitým akcentu"
+
+msgctxt "A label for the \"latin small letter u with double acute\" symbol."
+msgid "Latin small letter u with double acute"
+msgstr "Latinské malé písmeno u s dvojitým akcentu"
+
+msgctxt "A label for the \"latin capital letter u with ogonek\" symbol."
+msgid "Latin capital letter u with ogonek"
+msgstr "Latinské velké písmeno u s háčkem"
+
+msgctxt "A label for the \"latin small letter u with ogonek\" symbol."
+msgid "Latin small letter u with ogonek"
+msgstr "Latinské malé písmeno u s háčkem"
+
+msgctxt "A label for the \"latin capital letter w with circumflex\" symbol."
+msgid "Latin capital letter w with circumflex"
+msgstr "Latinské velké písmeno w s obráceným háčkem"
+
+msgctxt "A label for the \"latin small letter w with circumflex\" symbol."
+msgid "Latin small letter w with circumflex"
+msgstr "Latinské malé písmeno w s obráceným háčkem"
+
+msgctxt "A label for the \"latin capital letter y with circumflex\" symbol."
+msgid "Latin capital letter y with circumflex"
+msgstr "Latinské velké písmeno y s obráceným háčkem"
+
+msgctxt "A label for the \"latin small letter y with circumflex\" symbol."
+msgid "Latin small letter y with circumflex"
+msgstr "Latinské malé písmeno y s obráteným mäkčeňom"
+
+msgctxt "A label for the \"latin capital letter y with diaeresis\" symbol."
+msgid "Latin capital letter y with diaeresis"
+msgstr "Latinské velké písmeno y s dvojtečkou nad znakem"
+
+msgctxt "A label for the \"latin capital letter z with acute\" symbol."
+msgid "Latin capital letter z with acute"
+msgstr "Latinské velké písmeno z s čárkou"
+
+msgctxt "A label for the \"latin small letter z with acute\" symbol."
+msgid "Latin small letter z with acute"
+msgstr "Latinské malé písmeno z s čárkou"
+
+msgctxt "A label for the \"latin capital letter z with dot above\" symbol."
+msgid "Latin capital letter z with dot above"
+msgstr "Latinské velké písmeno z s tečkou nad znakem"
+
+msgctxt "A label for the \"latin small letter z with dot above\" symbol."
+msgid "Latin small letter z with dot above"
+msgstr "Latinské malé písmeno z s tečkou nad znakem"
+
+msgctxt "A label for the \"latin capital letter z with caron\" symbol."
+msgid "Latin capital letter z with caron"
+msgstr "Latinské velké písmeno z s háčkem"
+
+msgctxt "A label for the \"latin small letter z with caron\" symbol."
+msgid "Latin small letter z with caron"
+msgstr "Malé písmeno s z háčkem"
+
+msgctxt "A label for the \"latin small letter long s\" symbol."
+msgid "Latin small letter long s"
+msgstr "Malé dlouhé písmeno s"
+
+msgctxt "A label for the \"less-than sign\" symbol."
+msgid "Less-than sign"
+msgstr "Menší než"
+
+msgctxt "A label for the \"greater-than sign\" symbol."
+msgid "Greater-than sign"
+msgstr "Větší než"
+
+msgctxt "A label for the \"less-than or equal to\" symbol."
+msgid "Less-than or equal to"
+msgstr "Menší nebo roven"
+
+msgctxt "A label for the \"greater-than or equal to\" symbol."
+msgid "Greater-than or equal to"
+msgstr "Větší nebo roven"
+
+msgctxt "A label for the \"en dash\" symbol."
+msgid "En dash"
+msgstr "Pomlčka"
+
+msgctxt "A label for the \"em dash\" symbol."
+msgid "Em dash"
+msgstr "Dlouhá pomlčka"
+
+msgctxt "A label for the \"macron\" symbol."
+msgid "Macron"
+msgstr "Horní čára"
+
+msgctxt "A label for the \"overline\" symbol."
+msgid "Overline"
+msgstr "Přeškrtnutí"
+
+msgctxt "A label for the \"degree sign\" symbol."
+msgid "Degree sign"
+msgstr "Znak stupeň"
+
+msgctxt "A label for the \"minus sign\" symbol."
+msgid "Minus sign"
+msgstr "Znak mínus"
+
+msgctxt "A label for the \"plus-minus sign\" symbol."
+msgid "Plus-minus sign"
+msgstr "Znak plus-minus"
+
+msgctxt "A label for the \"division sign\" symbol."
+msgid "Division sign"
+msgstr "Dělení"
+
+msgctxt "A label for the \"fraction slash\" symbol."
+msgid "Fraction slash"
+msgstr "Lomítko / Dělení"
+
+msgctxt "A label for the \"multiplication sign\" symbol."
+msgid "Multiplication sign"
+msgstr "Násobení"
+
+msgctxt "A label for the \"latin small letter f with hook\" symbol."
+msgid "Latin small letter f with hook"
+msgstr "Funkce"
+
+msgctxt "A label for the \"integral\" symbol."
+msgid "Integral"
+msgstr "Integrál"
+
+msgctxt "A label for the \"n-ary summation\" symbol."
+msgid "N-ary summation"
+msgstr "Znak cyklického sčítání"
+
+msgctxt "A label for the \"infinity\" symbol."
+msgid "Infinity"
+msgstr "Nekonečno"
+
+msgctxt "A label for the \"square root\" symbol."
+msgid "Square root"
+msgstr "Odmocnina"
+
+msgctxt "A label for the \"tilde operator\" symbol."
+msgid "Tilde operator"
+msgstr "Vlnovka"
+
+msgctxt "A label for the \"approximately equal to\" symbol."
+msgid "Approximately equal to"
+msgstr "Aproximace"
+
+msgctxt "A label for the \"almost equal to\" symbol."
+msgid "Almost equal to"
+msgstr "Částečně rovný"
+
+msgctxt "A label for the \"not equal to\" symbol."
+msgid "Not equal to"
+msgstr "Nerovná se"
+
+msgctxt "A label for the \"identical to\" symbol."
+msgid "Identical to"
+msgstr "Identický k"
+
+msgctxt "A label for the \"element of\" symbol."
+msgid "Element of"
+msgstr "Patří / Je součástí"
+
+msgctxt "A label for the \"not an element of\" symbol."
+msgid "Not an element of"
+msgstr "Nepatří / Není součástí"
+
+msgctxt "A label for the \"contains as member\" symbol."
+msgid "Contains as member"
+msgstr "Obsahuje prvek"
+
+msgctxt "A label for the \"n-ary product\" symbol."
+msgid "N-ary product"
+msgstr "Znak cyklického násobení"
+
+msgctxt "A label for the \"logical and\" symbol."
+msgid "Logical and"
+msgstr "Logický AND"
+
+msgctxt "A label for the \"logical or\" symbol."
+msgid "Logical or"
+msgstr "Logický OR"
+
+msgctxt "A label for the \"not sign\" symbol."
+msgid "Not sign"
+msgstr "Není rovný"
+
+msgctxt "A label for the \"intersection\" symbol."
+msgid "Intersection"
+msgstr "Průsečík / Průnik"
+
+msgctxt "A label for the \"union\" symbol."
+msgid "Union"
+msgstr "Sjednocení"
+
+msgctxt "A label for the \"partial differential\" symbol."
+msgid "Partial differential"
+msgstr "Parciální diference"
+
+msgctxt "A label for the \"for all\" symbol."
+msgid "For all"
+msgstr "Pro všechny prvky v množině"
+
+msgctxt "A label for the \"there exists\" symbol."
+msgid "There exists"
+msgstr "Existuje v množině"
+
+msgctxt "A label for the \"empty set\" symbol."
+msgid "Empty set"
+msgstr "Prázdná množina"
+
+msgctxt "A label for the \"nabla\" symbol."
+msgid "Nabla"
+msgstr "Nabla"
+
+msgctxt "A label for the \"asterisk operator\" symbol."
+msgid "Asterisk operator"
+msgstr "Hvězdička / násobení"
+
+msgctxt "A label for the \"proportional to\" symbol."
+msgid "Proportional to"
+msgstr "Úměrný k"
+
+msgctxt "A label for the \"angle\" symbol."
+msgid "Angle"
+msgstr "Úhel"
+
+msgctxt "A label for the \"vulgar fraction one quarter\" symbol."
+msgid "Vulgar fraction one quarter"
+msgstr "Jedna čtvrtina"
+
+msgctxt "A label for the \"vulgar fraction one half\" symbol."
+msgid "Vulgar fraction one half"
+msgstr "Polovina"
+
+msgctxt "A label for the \"vulgar fraction three quarters\" symbol."
+msgid "Vulgar fraction three quarters"
+msgstr "Tři čtvrtiny"
+
+msgctxt "A label for the \"single left-pointing angle quotation mark\" symbol."
+msgid "Single left-pointing angle quotation mark"
+msgstr "Šipka ukazující do leva"
+
+msgctxt "A label for the \"single right-pointing angle quotation mark\" symbol."
+msgid "Single right-pointing angle quotation mark"
+msgstr "Šipka ukazující do prava"
+
+msgctxt "A label for the \"left-pointing double angle quotation mark\" symbol."
+msgid "Left-pointing double angle quotation mark"
+msgstr "Dvojitá šipka ukazující do leva"
+
+msgctxt "A label for the \"right-pointing double angle quotation mark\" symbol."
+msgid "Right-pointing double angle quotation mark"
+msgstr "Dvojitá šipka ukazující do prava"
+
+msgctxt "A label for the \"left single quotation mark\" symbol."
+msgid "Left single quotation mark"
+msgstr "Levá uvozovka"
+
+msgctxt "A label for the \"right single quotation mark\" symbol."
+msgid "Right single quotation mark"
+msgstr "Pravá uvozovka"
+
+msgctxt "A label for the \"left double quotation mark\" symbol."
+msgid "Left double quotation mark"
+msgstr "Levá dvojitá uvozovka"
+
+msgctxt "A label for the \"right double quotation mark\" symbol."
+msgid "Right double quotation mark"
+msgstr "Pravá dvojitá uvozovka"
+
+msgctxt "A label for the \"single low-9 quotation mark\" symbol."
+msgid "Single low-9 quotation mark"
+msgstr "Spodní uvozovka"
+
+msgctxt "A label for the \"double low-9 quotation mark\" symbol."
+msgid "Double low-9 quotation mark"
+msgstr "Dvojitá spodní uvozovka"
+
+msgctxt "A label for the \"inverted exclamation mark\" symbol."
+msgid "Inverted exclamation mark"
+msgstr "Obrácený vykřičník"
+
+msgctxt "A label for the \"inverted question mark\" symbol."
+msgid "Inverted question mark"
+msgstr "Obrácený otazník"
+
+msgctxt "A label for the \"two dot leader\" symbol."
+msgid "Two dot leader"
+msgstr "Horizontální dvojtečka"
+
+msgctxt "A label for the \"horizontal ellipsis\" symbol."
+msgid "Horizontal ellipsis"
+msgstr "Tečky"
+
+msgctxt "A label for the \"double dagger\" symbol."
+msgid "Double dagger"
+msgstr "Dvojkříž"
+
+msgctxt "A label for the \"per mille sign\" symbol."
+msgid "Per mille sign"
+msgstr "Promile"
+
+msgctxt "A label for the \"per ten thousand sign\" symbol."
+msgid "Per ten thousand sign"
+msgstr "Na deset tisíc"
+
+msgctxt "A label for the \"double exclamation mark\" symbol."
+msgid "Double exclamation mark"
+msgstr "Dvojitý vykřičník"
+
+msgctxt "A label for the \"question exclamation mark\" symbol."
+msgid "Question exclamation mark"
+msgstr "Otazník a vykřičník"
+
+msgctxt "A label for the \"exclamation question mark\" symbol."
+msgid "Exclamation question mark"
+msgstr "Vykřičník a otazník"
+
+msgctxt "A label for the \"double question mark\" symbol."
+msgid "Double question mark"
+msgstr "Dvojitý otazník"
+
+msgctxt "A label for the \"copyright sign\" symbol."
+msgid "Copyright sign"
+msgstr "Copyright"
+
+msgctxt "A label for the \"registered sign\" symbol."
+msgid "Registered sign"
+msgstr "Registrovaný"
+
+msgctxt "A label for the \"trade mark sign\" symbol."
+msgid "Trade mark sign"
+msgstr "Ochranná známka"
+
+msgctxt "A label for the \"section sign\" symbol."
+msgid "Section sign"
+msgstr "Sekce"
+
+msgctxt "A label for the \"paragraph sign\" symbol."
+msgid "Paragraph sign"
+msgstr "Odstavec"
+
+msgctxt "A label for the \"reversed paragraph sign\" symbol."
+msgid "Reversed paragraph sign"
+msgstr "Obrácený znak odstavce"
+
+msgctxt "A label for a tooltip showing when the user hovers over the plugin icon."
+msgid "Character categories"
+msgstr "Kategorie znaků"
diff --git a/packages/ckeditor5-special-characters/lang/translations/sk.po b/packages/ckeditor5-special-characters/lang/translations/sk.po
index ba7579b3534..ad2a80c6d08 100644
--- a/packages/ckeditor5-special-characters/lang/translations/sk.po
+++ b/packages/ckeditor5-special-characters/lang/translations/sk.po
@@ -22,1016 +22,1016 @@ msgstr "Špeciálne znaky"
msgctxt "A label for the \"leftwards double arrow\" symbol."
msgid "leftwards double arrow"
-msgstr ""
+msgstr "dvojitá šípka doľava"
msgctxt "A label for the \"rightwards double arrow\" symbol."
msgid "rightwards double arrow"
-msgstr ""
+msgstr "dvojitá šípka doprava"
msgctxt "A label for the \"upwards double arrow\" symbol."
msgid "upwards double arrow"
-msgstr ""
+msgstr "dvojitá šípka nahor"
msgctxt "A label for the \"downwards double arrow\" symbol."
msgid "downwards double arrow"
-msgstr ""
+msgstr "dvojitá šípka nadol"
msgctxt "A label for the \"leftwards dashed arrow\" symbol."
msgid "leftwards dashed arrow"
-msgstr ""
+msgstr "prerušovaná šípka doľava"
msgctxt "A label for the \"rightwards dashed arrow\" symbol."
msgid "rightwards dashed arrow"
-msgstr ""
+msgstr "čiarkovaná šípka doprava"
msgctxt "A label for the \"upwards dashed arrow\" symbol."
msgid "upwards dashed arrow"
-msgstr ""
+msgstr "čiarkovaná šípka nahor"
msgctxt "A label for the \"downwards dashed arrow\" symbol."
msgid "downwards dashed arrow"
-msgstr ""
+msgstr "prerušovaná šípka nadol"
msgctxt "A label for the \"leftwards arrow to bar\" symbol."
msgid "leftwards arrow to bar"
-msgstr ""
+msgstr "šípka doľava do zvislej čiary"
msgctxt "A label for the \"rightwards arrow to bar\" symbol."
msgid "rightwards arrow to bar"
-msgstr ""
+msgstr "šípka doprava do zvislej čiary"
msgctxt "A label for the \"upwards arrow to bar\" symbol."
msgid "upwards arrow to bar"
-msgstr ""
+msgstr "šípka nahor do zvislej čiary"
msgctxt "A label for the \"downwards arrow to bar\" symbol."
msgid "downwards arrow to bar"
-msgstr ""
+msgstr "šípka nadol do zvislej čiary"
msgctxt "A label for the \"up down arrow with base\" symbol."
msgid "up down arrow with base"
-msgstr ""
+msgstr "Šípka hore-dole od základne"
msgctxt "A label for the \"back with leftwards arrow above\" symbol."
msgid "back with leftwards arrow above"
-msgstr ""
+msgstr "Šípka späť"
msgctxt "A label for the \"end with leftwards arrow above\" symbol."
msgid "end with leftwards arrow above"
-msgstr ""
+msgstr "Šípka koniec"
msgctxt "A label for the \"on with exclamation mark with left right arrow above\" symbol."
msgid "on with exclamation mark with left right arrow above"
-msgstr ""
+msgstr "ON s výkričníkom so šípkou doľava doprava hore"
msgctxt "A label for the \"soon with rightwards arrow above\" symbol."
msgid "soon with rightwards arrow above"
-msgstr ""
+msgstr "čoskoro so šípkou doprava hore"
msgctxt "A label for the \"top with upwards arrow above\" symbol."
msgid "top with upwards arrow above"
-msgstr ""
+msgstr "TOP so šípkou hore"
msgctxt "A label for the \"dollar sign\" symbol."
msgid "Dollar sign"
-msgstr ""
+msgstr "Znak Dolár"
msgctxt "A label for the \"euro sign\" symbol."
msgid "Euro sign"
-msgstr ""
+msgstr "Znak Euro"
msgctxt "A label for the \"yen sign\" symbol."
msgid "Yen sign"
-msgstr ""
+msgstr "Znak Jen"
msgctxt "A label for the \"pound sign\" symbol."
msgid "Pound sign"
-msgstr ""
+msgstr "Znak Libra"
msgctxt "A label for the \"cent sign\" symbol."
msgid "Cent sign"
-msgstr ""
+msgstr "Znak cent"
msgctxt "A label for the \"euro-currency sign\" symbol."
msgid "Euro-currency sign"
-msgstr ""
+msgstr "Mena Euro"
msgctxt "A label for the \"colon sign\" symbol."
msgid "Colon sign"
-msgstr ""
+msgstr "Dvojbodka"
msgctxt "A label for the \"cruzeiro sign\" symbol."
msgid "Cruzeiro sign"
-msgstr ""
+msgstr "Mena Cruzeiro"
msgctxt "A label for the \"french franc sign\" symbol."
msgid "French franc sign"
-msgstr ""
+msgstr "Mena Francúzsky Frank"
msgctxt "A label for the \"lira sign\" symbol."
msgid "Lira sign"
-msgstr ""
+msgstr "Mena Líra"
msgctxt "A label for the \"currency sign\" symbol."
msgid "Currency sign"
-msgstr ""
+msgstr "Znak meny"
msgctxt "A label for the \"bitcoin sign\" symbol."
msgid "Bitcoin sign"
-msgstr ""
+msgstr "Mena Bitcoin"
msgctxt "A label for the \"mill sign\" symbol."
msgid "Mill sign"
-msgstr ""
+msgstr "Znak Mill"
msgctxt "A label for the \"naira sign\" symbol."
msgid "Naira sign"
-msgstr ""
+msgstr "Znak Naira"
msgctxt "A label for the \"peseta sign\" symbol."
msgid "Peseta sign"
-msgstr ""
+msgstr "Znak Peseta"
msgctxt "A label for the \"rupee sign\" symbol."
msgid "Rupee sign"
-msgstr ""
+msgstr "Znak Rupee"
msgctxt "A label for the \"won sign\" symbol."
msgid "Won sign"
-msgstr ""
+msgstr "Znak Won"
msgctxt "A label for the \"new sheqel sign\" symbol."
msgid "New sheqel sign"
-msgstr ""
+msgstr "Nový znak šekelu"
msgctxt "A label for the \"dong sign\" symbol."
msgid "Dong sign"
-msgstr ""
+msgstr "Znak Dong"
msgctxt "A label for the \"kip sign\" symbol."
msgid "Kip sign"
-msgstr ""
+msgstr "Znak Kip"
msgctxt "A label for the \"tugrik sign\" symbol."
msgid "Tugrik sign"
-msgstr ""
+msgstr "Znak Tugrik"
msgctxt "A label for the \"drachma sign\" symbol."
msgid "Drachma sign"
-msgstr ""
+msgstr "Znak Drachma"
msgctxt "A label for the \"german penny sign\" symbol."
msgid "German penny sign"
-msgstr ""
+msgstr "Nemecká penny"
msgctxt "A label for the \"peso sign\" symbol."
msgid "Peso sign"
-msgstr ""
+msgstr "Znak Peso"
msgctxt "A label for the \"guarani sign\" symbol."
msgid "Guarani sign"
-msgstr ""
+msgstr "Znak Guarani"
msgctxt "A label for the \"austral sign\" symbol."
msgid "Austral sign"
-msgstr ""
+msgstr "Znak Austral"
msgctxt "A label for the \"hryvnia sign\" symbol."
msgid "Hryvnia sign"
-msgstr ""
+msgstr "Znak Hryvnia"
msgctxt "A label for the \"cedi sign\" symbol."
msgid "Cedi sign"
-msgstr ""
+msgstr "Znak Cedi"
msgctxt "A label for the \"livre tournois sign\" symbol."
msgid "Livre tournois sign"
-msgstr ""
+msgstr "Znak Livre tournois"
msgctxt "A label for the \"spesmilo sign\" symbol."
msgid "Spesmilo sign"
-msgstr ""
+msgstr "Znak Spesmilo"
msgctxt "A label for the \"tenge sign\" symbol."
msgid "Tenge sign"
-msgstr ""
+msgstr "Znak Tenge"
msgctxt "A label for the \"indian rupee sign\" symbol."
msgid "Indian rupee sign"
-msgstr ""
+msgstr "Znak Indická rupia"
msgctxt "A label for the \"turkish lira sign\" symbol."
msgid "Turkish lira sign"
-msgstr ""
+msgstr "Znak Turecká líra"
msgctxt "A label for the \"nordic mark sign\" symbol."
msgid "Nordic mark sign"
-msgstr ""
+msgstr "Znak Nórska marka"
msgctxt "A label for the \"manat sign\" symbol."
msgid "Manat sign"
-msgstr ""
+msgstr "Znak Manat"
msgctxt "A label for the \"ruble sign\" symbol."
msgid "Ruble sign"
-msgstr ""
+msgstr "Znak Ruble"
msgctxt "A label for the \"latin capital letter a with macron\" symbol."
msgid "Latin capital letter a with macron"
-msgstr ""
+msgstr "Latinské veľké písmeno a s čiarou"
msgctxt "A label for the \"latin small letter a with macron\" symbol."
msgid "Latin small letter a with macron"
-msgstr ""
+msgstr "Latinské malé písmeno a s čiarou"
msgctxt "A label for the \"latin capital letter a with breve\" symbol."
msgid "Latin capital letter a with breve"
-msgstr ""
+msgstr "Latinské veľké písmeno a s mäkčeňom"
msgctxt "A label for the \"latin small letter a with breve\" symbol."
msgid "Latin small letter a with breve"
-msgstr ""
+msgstr "Latinské malé písmeno a s mäkčeňom"
msgctxt "A label for the \"latin capital letter a with ogonek\" symbol."
msgid "Latin capital letter a with ogonek"
-msgstr ""
+msgstr "Latinské veľké písmeno a s háčikom"
msgctxt "A label for the \"latin small letter a with ogonek\" symbol."
msgid "Latin small letter a with ogonek"
-msgstr ""
+msgstr "Latinské malé písmeno a s háčikom"
msgctxt "A label for the \"latin capital letter c with acute\" symbol."
msgid "Latin capital letter c with acute"
-msgstr ""
+msgstr "Latinské veľké písmeno c s dĺžňom"
msgctxt "A label for the \"latin small letter c with acute\" symbol."
msgid "Latin small letter c with acute"
-msgstr ""
+msgstr "Latinské malé písmeno c s dĺžňom"
msgctxt "A label for the \"latin capital letter c with circumflex\" symbol."
msgid "Latin capital letter c with circumflex"
-msgstr ""
+msgstr "Latinské veľké písmeno c s obráteným mäkčeňom"
msgctxt "A label for the \"latin small letter c with circumflex\" symbol."
msgid "Latin small letter c with circumflex"
-msgstr ""
+msgstr "Latinské malé písmeno c s obráteným mäkčeňom"
msgctxt "A label for the \"latin capital letter c with dot above\" symbol."
msgid "Latin capital letter c with dot above"
-msgstr ""
+msgstr "Latinské veľké písmeno c s bodkou nad znakom"
msgctxt "A label for the \"latin small letter c with dot above\" symbol."
msgid "Latin small letter c with dot above"
-msgstr ""
+msgstr "Latinské malé písmeno c s bodkou nad znakom"
msgctxt "A label for the \"latin capital letter c with caron\" symbol."
msgid "Latin capital letter c with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno c s mäkčeňom"
msgctxt "A label for the \"latin small letter c with caron\" symbol."
msgid "Latin small letter c with caron"
-msgstr ""
+msgstr "Latinské malé písmeno c s mäkčeňom"
msgctxt "A label for the \"latin capital letter d with caron\" symbol."
msgid "Latin capital letter d with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno d s mäkčeňom"
msgctxt "A label for the \"latin small letter d with caron\" symbol."
msgid "Latin small letter d with caron"
-msgstr ""
+msgstr "Latinské malé písmeno d s mäkčeňom"
msgctxt "A label for the \"latin capital letter d with stroke\" symbol."
msgid "Latin capital letter d with stroke"
-msgstr ""
+msgstr "Latinské veľké písmeno d s prečiarknutím"
msgctxt "A label for the \"latin small letter d with stroke\" symbol."
msgid "Latin small letter d with stroke"
-msgstr ""
+msgstr "Latinské malé písmeno d s prečiarknutím"
msgctxt "A label for the \"latin capital letter e with macron\" symbol."
msgid "Latin capital letter e with macron"
-msgstr ""
+msgstr "Latinské veľké písmeno e s čiarou"
msgctxt "A label for the \"latin small letter e with macron\" symbol."
msgid "Latin small letter e with macron"
-msgstr ""
+msgstr "Latinské malé písmeno e s čiarou"
msgctxt "A label for the \"latin capital letter e with breve\" symbol."
msgid "Latin capital letter e with breve"
-msgstr ""
+msgstr "Latinské veľké písmeno e s mäkčeňom"
msgctxt "A label for the \"latin small letter e with breve\" symbol."
msgid "Latin small letter e with breve"
-msgstr ""
+msgstr "Latinské malé písmeno e s mäkčeňom"
msgctxt "A label for the \"latin capital letter e with dot above\" symbol."
msgid "Latin capital letter e with dot above"
-msgstr ""
+msgstr "Latinské veľké písmeno e s bodkou nad znakom"
msgctxt "A label for the \"latin small letter e with dot above\" symbol."
msgid "Latin small letter e with dot above"
-msgstr ""
+msgstr "Latinské malé písmeno e s bodkou nad znakom"
msgctxt "A label for the \"latin capital letter e with ogonek\" symbol."
msgid "Latin capital letter e with ogonek"
-msgstr ""
+msgstr "Latinské veľké písmeno e s háčikom"
msgctxt "A label for the \"latin small letter e with ogonek\" symbol."
msgid "Latin small letter e with ogonek"
-msgstr ""
+msgstr "Latinské malé písmeno e s háčikom"
msgctxt "A label for the \"latin capital letter e with caron\" symbol."
msgid "Latin capital letter e with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno e s mäkčeňom"
msgctxt "A label for the \"latin small letter e with caron\" symbol."
msgid "Latin small letter e with caron"
-msgstr ""
+msgstr "Latinské malé písmeno e s mäkčeňom"
msgctxt "A label for the \"latin capital letter g with circumflex\" symbol."
msgid "Latin capital letter g with circumflex"
-msgstr ""
+msgstr "Latinské veľké písmeno g s obráteným mäkčeňom"
msgctxt "A label for the \"latin small letter g with circumflex\" symbol."
msgid "Latin small letter g with circumflex"
-msgstr ""
+msgstr "Latinské malé písmeno g s obráteným mäkčeňom"
msgctxt "A label for the \"latin capital letter g with breve\" symbol."
msgid "Latin capital letter g with breve"
-msgstr ""
+msgstr "Latinské veľké písmeno g s mäkčeňom"
msgctxt "A label for the \"latin small letter g with breve\" symbol."
msgid "Latin small letter g with breve"
-msgstr ""
+msgstr "Latinské malé písmeno g s mäkčeňom"
msgctxt "A label for the \"latin capital letter g with dot above\" symbol."
msgid "Latin capital letter g with dot above"
-msgstr ""
+msgstr "Latinské veľké písmeno g s bodkou nad znakom"
msgctxt "A label for the \"latin small letter g with dot above\" symbol."
msgid "Latin small letter g with dot above"
-msgstr ""
+msgstr "Latinské malé písmeno g s bodkou nad znakom"
msgctxt "A label for the \"latin capital letter g with cedilla\" symbol."
msgid "Latin capital letter g with cedilla"
-msgstr ""
+msgstr "Latinské veľké písmeno g s háčikom"
msgctxt "A label for the \"latin small letter g with cedilla\" symbol."
msgid "Latin small letter g with cedilla"
-msgstr ""
+msgstr "Latinské malé písmeno g s háčikom"
msgctxt "A label for the \"latin capital letter h with circumflex\" symbol."
msgid "Latin capital letter h with circumflex"
-msgstr ""
+msgstr "Latinské veľké písmeno h s obráteným mäkčeňom"
msgctxt "A label for the \"latin small letter h with circumflex\" symbol."
msgid "Latin small letter h with circumflex"
-msgstr ""
+msgstr "Latinské malé písmeno h s obráteným mäkčeňom"
msgctxt "A label for the \"latin capital letter h with stroke\" symbol."
msgid "Latin capital letter h with stroke"
-msgstr ""
+msgstr "Latinské veľké písmeno h s prečiarknutím"
msgctxt "A label for the \"latin small letter h with stroke\" symbol."
msgid "Latin small letter h with stroke"
-msgstr ""
+msgstr "Latinské malé písmeno h s prečiarknutím"
msgctxt "A label for the \"latin capital letter i with tilde\" symbol."
msgid "Latin capital letter i with tilde"
-msgstr ""
+msgstr "Latinské veľké písmeno i s vlnovkou"
msgctxt "A label for the \"latin small letter i with tilde\" symbol."
msgid "Latin small letter i with tilde"
-msgstr ""
+msgstr "Latinské malé písmeno i s vlnovkou"
msgctxt "A label for the \"latin capital letter i with macron\" symbol."
msgid "Latin capital letter i with macron"
-msgstr ""
+msgstr "Latinské veľké písmeno i s čiarou"
msgctxt "A label for the \"latin small letter i with macron\" symbol."
msgid "Latin small letter i with macron"
-msgstr ""
+msgstr "Latinské malé písmeno i s čiarou"
msgctxt "A label for the \"latin capital letter i with breve\" symbol."
msgid "Latin capital letter i with breve"
-msgstr ""
+msgstr "Latinské veľké písmeno i s mäkčeňom"
msgctxt "A label for the \"latin small letter i with breve\" symbol."
msgid "Latin small letter i with breve"
-msgstr ""
+msgstr "Latinské malé písmeno i s mäkčeňom"
msgctxt "A label for the \"latin capital letter i with ogonek\" symbol."
msgid "Latin capital letter i with ogonek"
-msgstr ""
+msgstr "Latinské veľké písmeno i s háčikom"
msgctxt "A label for the \"latin small letter i with ogonek\" symbol."
msgid "Latin small letter i with ogonek"
-msgstr ""
+msgstr "Latinské malé písmeno i s háčikom"
msgctxt "A label for the \"latin capital letter i with dot above\" symbol."
msgid "Latin capital letter i with dot above"
-msgstr ""
+msgstr "Latinské veľké písmeno i s bodkou nad znakom"
msgctxt "A label for the \"latin small letter dotless i\" symbol."
msgid "Latin small letter dotless i"
-msgstr ""
+msgstr "Latinské malé písmeno i bez bodky"
msgctxt "A label for the \"latin capital ligature ij\" symbol."
msgid "Latin capital ligature ij"
-msgstr ""
+msgstr "Latinský veľký znak ligatúry ij"
msgctxt "A label for the \"latin small ligature ij\" symbol."
msgid "Latin small ligature ij"
-msgstr ""
+msgstr "Latinský malý znak ligatúry ij"
msgctxt "A label for the \"latin capital letter j with circumflex\" symbol."
msgid "Latin capital letter j with circumflex"
-msgstr ""
+msgstr "Latinské veľké písmeno j s obráteným mäkčeňom"
msgctxt "A label for the \"latin small letter j with circumflex\" symbol."
msgid "Latin small letter j with circumflex"
-msgstr ""
+msgstr "Latinské malé písmeno j s obráteným mäkčeňom"
msgctxt "A label for the \"latin capital letter k with cedilla\" symbol."
msgid "Latin capital letter k with cedilla"
-msgstr ""
+msgstr "Latinské veľké písmeno k s háčikom"
msgctxt "A label for the \"latin small letter k with cedilla\" symbol."
msgid "Latin small letter k with cedilla"
-msgstr ""
+msgstr "Latinské malé písmeno k s háčikom"
msgctxt "A label for the \"latin small letter kra\" symbol."
msgid "Latin small letter kra"
-msgstr ""
+msgstr "latinský malý znak Kra"
msgctxt "A label for the \"latin capital letter l with acute\" symbol."
msgid "Latin capital letter l with acute"
-msgstr ""
+msgstr "Latinské veľké písmeno l s dĺžňom"
msgctxt "A label for the \"latin small letter l with acute\" symbol."
msgid "Latin small letter l with acute"
-msgstr ""
+msgstr "Latinské malé písmeno l s dĺžňom"
msgctxt "A label for the \"latin capital letter l with cedilla\" symbol."
msgid "Latin capital letter l with cedilla"
-msgstr ""
+msgstr "Latinské veľké písmeno l s háčikom"
msgctxt "A label for the \"latin small letter l with cedilla\" symbol."
msgid "Latin small letter l with cedilla"
-msgstr ""
+msgstr "Latinské malé písmeno l s háčikom"
msgctxt "A label for the \"latin capital letter l with caron\" symbol."
msgid "Latin capital letter l with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno l s mäkčeňom"
msgctxt "A label for the \"latin small letter l with caron\" symbol."
msgid "Latin small letter l with caron"
-msgstr ""
+msgstr "Latinské malé písmeno l s mäkčeňom"
msgctxt "A label for the \"latin capital letter l with middle dot\" symbol."
msgid "Latin capital letter l with middle dot"
-msgstr ""
+msgstr "Latinské veľké písmeno l s bodkou uprostred"
msgctxt "A label for the \"latin small letter l with middle dot\" symbol."
msgid "Latin small letter l with middle dot"
-msgstr ""
+msgstr "Latinské malé písmeno l s bodkou uprostred"
msgctxt "A label for the \"latin capital letter l with stroke\" symbol."
msgid "Latin capital letter l with stroke"
-msgstr ""
+msgstr "Latinské veľké písmeno l s prečiarknutím"
msgctxt "A label for the \"latin small letter l with stroke\" symbol."
msgid "Latin small letter l with stroke"
-msgstr ""
+msgstr "Latinské malé písmeno l s prečiarknutím"
msgctxt "A label for the \"latin capital letter n with acute\" symbol."
msgid "Latin capital letter n with acute"
-msgstr ""
+msgstr "Latinské veľké písmeno n s dĺžňom"
msgctxt "A label for the \"latin small letter n with acute\" symbol."
msgid "Latin small letter n with acute"
-msgstr ""
+msgstr "Latinské malé písmeno n s dĺžňom"
msgctxt "A label for the \"latin capital letter n with cedilla\" symbol."
msgid "Latin capital letter n with cedilla"
-msgstr ""
+msgstr "Latinské veľké písmeno n s háčikom"
msgctxt "A label for the \"latin small letter n with cedilla\" symbol."
msgid "Latin small letter n with cedilla"
-msgstr ""
+msgstr "Latinské malé písmeno n s háčikom"
msgctxt "A label for the \"latin capital letter n with caron\" symbol."
msgid "Latin capital letter n with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno n s mäkčeňom"
msgctxt "A label for the \"latin small letter n with caron\" symbol."
msgid "Latin small letter n with caron"
-msgstr ""
+msgstr "Latinské malé písmeno n s mäkčeňom"
msgctxt "A label for the \"latin small letter n preceded by apostrophe\" symbol."
msgid "Latin small letter n preceded by apostrophe"
-msgstr ""
+msgstr "Latinské malé písmeno n s apostrofom"
msgctxt "A label for the \"latin capital letter eng\" symbol."
msgid "Latin capital letter eng"
-msgstr ""
+msgstr "Latinské veľké písmeno Eng"
msgctxt "A label for the \"latin small letter eng\" symbol."
msgid "Latin small letter eng"
-msgstr ""
+msgstr "Latinské malé písmeno Eng"
msgctxt "A label for the \"latin capital letter o with macron\" symbol."
msgid "Latin capital letter o with macron"
-msgstr ""
+msgstr "Latinské veľké písmeno o s čiarou"
msgctxt "A label for the \"latin small letter o with macron\" symbol."
msgid "Latin small letter o with macron"
-msgstr ""
+msgstr "Latinské malé písmeno o s čiarou"
msgctxt "A label for the \"latin capital letter o with breve\" symbol."
msgid "Latin capital letter o with breve"
-msgstr ""
+msgstr "Latinské veľké písmeno o s mäkčeňom"
msgctxt "A label for the \"latin small letter o with breve\" symbol."
msgid "Latin small letter o with breve"
-msgstr ""
+msgstr "Latinské malé písmeno o s mäkčeňom"
msgctxt "A label for the \"latin capital letter o with double acute\" symbol."
msgid "Latin capital letter o with double acute"
-msgstr ""
+msgstr "Latinské veľké písmeno o s dĺžňom"
msgctxt "A label for the \"latin small letter o with double acute\" symbol."
msgid "Latin small letter o with double acute"
-msgstr ""
+msgstr "Latinské malé písmeno o s dĺžňom"
msgctxt "A label for the \"latin capital ligature oe\" symbol."
msgid "Latin capital ligature oe"
-msgstr ""
+msgstr "Latinský veľký znak ligatúry oe"
msgctxt "A label for the \"latin small ligature oe\" symbol."
msgid "Latin small ligature oe"
-msgstr ""
+msgstr "Latinský malý znak ligatúry oe"
msgctxt "A label for the \"latin capital letter r with acute\" symbol."
msgid "Latin capital letter r with acute"
-msgstr ""
+msgstr "Latinské veľké písmeno r s dĺžňom"
msgctxt "A label for the \"latin small letter r with acute\" symbol."
msgid "Latin small letter r with acute"
-msgstr ""
+msgstr "Latinské malé písmeno r s dĺžňom"
msgctxt "A label for the \"latin capital letter r with cedilla\" symbol."
msgid "Latin capital letter r with cedilla"
-msgstr ""
+msgstr "Latinské veľké písmeno r s háčikom"
msgctxt "A label for the \"latin small letter r with cedilla\" symbol."
msgid "Latin small letter r with cedilla"
-msgstr ""
+msgstr "Latinské malé písmeno r s háčikom"
msgctxt "A label for the \"latin capital letter r with caron\" symbol."
msgid "Latin capital letter r with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno r s mäkčeňom"
msgctxt "A label for the \"latin small letter r with caron\" symbol."
msgid "Latin small letter r with caron"
-msgstr ""
+msgstr "Latinské malé písmeno r s mäkčeňom"
msgctxt "A label for the \"latin capital letter s with acute\" symbol."
msgid "Latin capital letter s with acute"
-msgstr ""
+msgstr "Latinské veľké písmeno s s dĺžňom"
msgctxt "A label for the \"latin small letter s with acute\" symbol."
msgid "Latin small letter s with acute"
-msgstr ""
+msgstr "Latinské malé písmeno s s dĺžňom"
msgctxt "A label for the \"latin capital letter s with circumflex\" symbol."
msgid "Latin capital letter s with circumflex"
-msgstr ""
+msgstr "Latinské veľké písmeno s s obráteným mäkčeňom"
msgctxt "A label for the \"latin small letter s with circumflex\" symbol."
msgid "Latin small letter s with circumflex"
-msgstr ""
+msgstr "Latinské malé písmeno s s obráteným mäkčeňom"
msgctxt "A label for the \"latin capital letter s with cedilla\" symbol."
msgid "Latin capital letter s with cedilla"
-msgstr ""
+msgstr "Latinské veľké písmeno s s háčikom"
msgctxt "A label for the \"latin small letter s with cedilla\" symbol."
msgid "Latin small letter s with cedilla"
-msgstr ""
+msgstr "Latinské malé písmeno s s háčikom"
msgctxt "A label for the \"latin capital letter s with caron\" symbol."
msgid "Latin capital letter s with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno s s mäkčeňom"
msgctxt "A label for the \"latin small letter s with caron\" symbol."
msgid "Latin small letter s with caron"
-msgstr ""
+msgstr "Latinské malé písmeno s s mäkčeňom"
msgctxt "A label for the \"latin capital letter t with cedilla\" symbol."
msgid "Latin capital letter t with cedilla"
-msgstr ""
+msgstr "Latinské veľké písmeno t s háčikom"
msgctxt "A label for the \"latin small letter t with cedilla\" symbol."
msgid "Latin small letter t with cedilla"
-msgstr ""
+msgstr "Latinské malé písmeno t s háčikom"
msgctxt "A label for the \"latin capital letter t with caron\" symbol."
msgid "Latin capital letter t with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno t s mäkčeňom"
msgctxt "A label for the \"latin small letter t with caron\" symbol."
msgid "Latin small letter t with caron"
-msgstr ""
+msgstr "Latinské malé písmeno t s mäkčeňom"
msgctxt "A label for the \"latin capital letter t with stroke\" symbol."
msgid "Latin capital letter t with stroke"
-msgstr ""
+msgstr "Latinské veľké písmeno t s prečiarknutím"
msgctxt "A label for the \"latin small letter t with stroke\" symbol."
msgid "Latin small letter t with stroke"
-msgstr ""
+msgstr "Latinské malé písmeno t s prečiarknutím"
msgctxt "A label for the \"latin capital letter u with tilde\" symbol."
msgid "Latin capital letter u with tilde"
-msgstr ""
+msgstr "Latinské veľké písmeno u s vlnovkou"
msgctxt "A label for the \"latin small letter u with tilde\" symbol."
msgid "Latin small letter u with tilde"
-msgstr ""
+msgstr "Latinské malé písmeno u s vlnovkou"
msgctxt "A label for the \"latin capital letter u with macron\" symbol."
msgid "Latin capital letter u with macron"
-msgstr ""
+msgstr "Latinské veľké písmeno u s čiarou"
msgctxt "A label for the \"latin small letter u with macron\" symbol."
msgid "Latin small letter u with macron"
-msgstr ""
+msgstr "Latinské malé písmeno o s čiarou"
msgctxt "A label for the \"latin capital letter u with breve\" symbol."
msgid "Latin capital letter u with breve"
-msgstr ""
+msgstr "Latinské veľké písmeno u s mäkčeňom"
msgctxt "A label for the \"latin small letter u with breve\" symbol."
msgid "Latin small letter u with breve"
-msgstr ""
+msgstr "Latinské malé písmeno u s mäkčeňom"
msgctxt "A label for the \"latin capital letter u with ring above\" symbol."
msgid "Latin capital letter u with ring above"
-msgstr ""
+msgstr "Latinské veľké písmeno u s krúžkom nad znakom"
msgctxt "A label for the \"latin small letter u with ring above\" symbol."
msgid "Latin small letter u with ring above"
-msgstr ""
+msgstr "Latinské malé písmeno u s krúžkom nad znakom"
msgctxt "A label for the \"latin capital letter u with double acute\" symbol."
msgid "Latin capital letter u with double acute"
-msgstr ""
+msgstr "Latinské veľké písmeno u s dvojitým dĺžňom"
msgctxt "A label for the \"latin small letter u with double acute\" symbol."
msgid "Latin small letter u with double acute"
-msgstr ""
+msgstr "Latinské malé písmeno u s dvojitým dĺžňom"
msgctxt "A label for the \"latin capital letter u with ogonek\" symbol."
msgid "Latin capital letter u with ogonek"
-msgstr ""
+msgstr "Latinské veľké písmeno u s háčikom"
msgctxt "A label for the \"latin small letter u with ogonek\" symbol."
msgid "Latin small letter u with ogonek"
-msgstr ""
+msgstr "Latinské malé písmeno u s háčikom"
msgctxt "A label for the \"latin capital letter w with circumflex\" symbol."
msgid "Latin capital letter w with circumflex"
-msgstr ""
+msgstr "Latinské veľké písmeno w s obráteným mäkčeňom"
msgctxt "A label for the \"latin small letter w with circumflex\" symbol."
msgid "Latin small letter w with circumflex"
-msgstr ""
+msgstr "Latinské malé písmeno w s obráteným mäkčeňom"
msgctxt "A label for the \"latin capital letter y with circumflex\" symbol."
msgid "Latin capital letter y with circumflex"
-msgstr ""
+msgstr "Latinské veľké písmeno y s obráteným mäkčeňom"
msgctxt "A label for the \"latin small letter y with circumflex\" symbol."
msgid "Latin small letter y with circumflex"
-msgstr ""
+msgstr "Latinské malé písmeno y s obráteným mäkčeňom"
msgctxt "A label for the \"latin capital letter y with diaeresis\" symbol."
msgid "Latin capital letter y with diaeresis"
-msgstr ""
+msgstr "Latinské veľké písmeno y s dvojbodkou nad znakom"
msgctxt "A label for the \"latin capital letter z with acute\" symbol."
msgid "Latin capital letter z with acute"
-msgstr ""
+msgstr "Latinské veľké písmeno z s dĺžňom"
msgctxt "A label for the \"latin small letter z with acute\" symbol."
msgid "Latin small letter z with acute"
-msgstr ""
+msgstr "Latinské malé písmeno z s dĺžňom"
msgctxt "A label for the \"latin capital letter z with dot above\" symbol."
msgid "Latin capital letter z with dot above"
-msgstr ""
+msgstr "Latinské veľké písmeno z s bodkou nad znakom"
msgctxt "A label for the \"latin small letter z with dot above\" symbol."
msgid "Latin small letter z with dot above"
-msgstr ""
+msgstr "Latinské malé písmeno z s bodkou nad znakom"
msgctxt "A label for the \"latin capital letter z with caron\" symbol."
msgid "Latin capital letter z with caron"
-msgstr ""
+msgstr "Latinské veľké písmeno z s mäkčeňom"
msgctxt "A label for the \"latin small letter z with caron\" symbol."
msgid "Latin small letter z with caron"
-msgstr ""
+msgstr "Malé písmeno s z mäkčeňom"
msgctxt "A label for the \"latin small letter long s\" symbol."
msgid "Latin small letter long s"
-msgstr ""
+msgstr "Malé dlhé písmeno s"
msgctxt "A label for the \"less-than sign\" symbol."
msgid "Less-than sign"
-msgstr ""
+msgstr "Menší ako"
msgctxt "A label for the \"greater-than sign\" symbol."
msgid "Greater-than sign"
-msgstr ""
+msgstr "Väčší ako"
msgctxt "A label for the \"less-than or equal to\" symbol."
msgid "Less-than or equal to"
-msgstr ""
+msgstr "Menší alebo rovný"
msgctxt "A label for the \"greater-than or equal to\" symbol."
msgid "Greater-than or equal to"
-msgstr ""
+msgstr "Väčší alebo rovný"
msgctxt "A label for the \"en dash\" symbol."
msgid "En dash"
-msgstr ""
+msgstr "Pomĺčka"
msgctxt "A label for the \"em dash\" symbol."
msgid "Em dash"
-msgstr ""
+msgstr "Dlhá pomĺčka"
msgctxt "A label for the \"macron\" symbol."
msgid "Macron"
-msgstr ""
+msgstr "Horná čiara"
msgctxt "A label for the \"overline\" symbol."
msgid "Overline"
-msgstr ""
+msgstr "Preškrtnutie"
msgctxt "A label for the \"degree sign\" symbol."
msgid "Degree sign"
-msgstr ""
+msgstr "Znak stupeň"
msgctxt "A label for the \"minus sign\" symbol."
msgid "Minus sign"
-msgstr ""
+msgstr "Znak mínus"
msgctxt "A label for the \"plus-minus sign\" symbol."
msgid "Plus-minus sign"
-msgstr ""
+msgstr "Znak plus-mínus"
msgctxt "A label for the \"division sign\" symbol."
msgid "Division sign"
-msgstr ""
+msgstr "Delenie"
msgctxt "A label for the \"fraction slash\" symbol."
msgid "Fraction slash"
-msgstr ""
+msgstr "Lomítko / Delenie"
msgctxt "A label for the \"multiplication sign\" symbol."
msgid "Multiplication sign"
-msgstr ""
+msgstr "Násobenie"
msgctxt "A label for the \"latin small letter f with hook\" symbol."
msgid "Latin small letter f with hook"
-msgstr ""
+msgstr "Funkcia"
msgctxt "A label for the \"integral\" symbol."
msgid "Integral"
-msgstr ""
+msgstr "Integrál"
msgctxt "A label for the \"n-ary summation\" symbol."
msgid "N-ary summation"
-msgstr ""
+msgstr "Znak cyklického sčítania"
msgctxt "A label for the \"infinity\" symbol."
msgid "Infinity"
-msgstr ""
+msgstr "Nekonečno"
msgctxt "A label for the \"square root\" symbol."
msgid "Square root"
-msgstr ""
+msgstr "Odmocnina"
msgctxt "A label for the \"tilde operator\" symbol."
msgid "Tilde operator"
-msgstr ""
+msgstr "Vlnovka"
msgctxt "A label for the \"approximately equal to\" symbol."
msgid "Approximately equal to"
-msgstr ""
+msgstr "Aproximácia"
msgctxt "A label for the \"almost equal to\" symbol."
msgid "Almost equal to"
-msgstr ""
+msgstr "Čiastočne rovný"
msgctxt "A label for the \"not equal to\" symbol."
msgid "Not equal to"
-msgstr ""
+msgstr "Nerovná sa"
msgctxt "A label for the \"identical to\" symbol."
msgid "Identical to"
-msgstr ""
+msgstr "Identický k"
msgctxt "A label for the \"element of\" symbol."
msgid "Element of"
-msgstr ""
+msgstr "Patrí / Je súčasťou"
msgctxt "A label for the \"not an element of\" symbol."
msgid "Not an element of"
-msgstr ""
+msgstr "Nepatrí / Nie je súčasťou"
msgctxt "A label for the \"contains as member\" symbol."
msgid "Contains as member"
-msgstr ""
+msgstr "Obsahuje prvok"
msgctxt "A label for the \"n-ary product\" symbol."
msgid "N-ary product"
-msgstr ""
+msgstr "Znak cyklického násobenia"
msgctxt "A label for the \"logical and\" symbol."
msgid "Logical and"
-msgstr ""
+msgstr "Logický AND"
msgctxt "A label for the \"logical or\" symbol."
msgid "Logical or"
-msgstr ""
+msgstr "Logický OR"
msgctxt "A label for the \"not sign\" symbol."
msgid "Not sign"
-msgstr ""
+msgstr "Nie je rovný"
msgctxt "A label for the \"intersection\" symbol."
msgid "Intersection"
-msgstr ""
+msgstr "Priesečník / Prienik"
msgctxt "A label for the \"union\" symbol."
msgid "Union"
-msgstr ""
+msgstr "Zjednotenie"
msgctxt "A label for the \"partial differential\" symbol."
msgid "Partial differential"
-msgstr ""
+msgstr "Parciálna diferencia"
msgctxt "A label for the \"for all\" symbol."
msgid "For all"
-msgstr ""
+msgstr "Pre všetky prvky v množine"
msgctxt "A label for the \"there exists\" symbol."
msgid "There exists"
-msgstr ""
+msgstr "Existuje v množine"
msgctxt "A label for the \"empty set\" symbol."
msgid "Empty set"
-msgstr ""
+msgstr "Prázdna množina"
msgctxt "A label for the \"nabla\" symbol."
msgid "Nabla"
-msgstr ""
+msgstr "Nabla"
msgctxt "A label for the \"asterisk operator\" symbol."
msgid "Asterisk operator"
-msgstr ""
+msgstr "Hviezdička / násobenie"
msgctxt "A label for the \"proportional to\" symbol."
msgid "Proportional to"
-msgstr ""
+msgstr "Úmerný k"
msgctxt "A label for the \"angle\" symbol."
msgid "Angle"
-msgstr ""
+msgstr "Uhol"
msgctxt "A label for the \"vulgar fraction one quarter\" symbol."
msgid "Vulgar fraction one quarter"
-msgstr ""
+msgstr "Jedna štvrtina"
msgctxt "A label for the \"vulgar fraction one half\" symbol."
msgid "Vulgar fraction one half"
-msgstr ""
+msgstr "Polovica"
msgctxt "A label for the \"vulgar fraction three quarters\" symbol."
msgid "Vulgar fraction three quarters"
-msgstr ""
+msgstr "Tri štvrtiny"
msgctxt "A label for the \"single left-pointing angle quotation mark\" symbol."
msgid "Single left-pointing angle quotation mark"
-msgstr ""
+msgstr "Šípka ukazujúca doľava"
msgctxt "A label for the \"single right-pointing angle quotation mark\" symbol."
msgid "Single right-pointing angle quotation mark"
-msgstr ""
+msgstr "Šípka ukazujúca doprava"
msgctxt "A label for the \"left-pointing double angle quotation mark\" symbol."
msgid "Left-pointing double angle quotation mark"
-msgstr ""
+msgstr "Dvojitá šípka ukazujúca doľava"
msgctxt "A label for the \"right-pointing double angle quotation mark\" symbol."
msgid "Right-pointing double angle quotation mark"
-msgstr ""
+msgstr "Dvojitá šípka ukazujúca doprava"
msgctxt "A label for the \"left single quotation mark\" symbol."
msgid "Left single quotation mark"
-msgstr ""
+msgstr "Ľavá uvodzovka"
msgctxt "A label for the \"right single quotation mark\" symbol."
msgid "Right single quotation mark"
-msgstr ""
+msgstr "Pravá uvodzovka"
msgctxt "A label for the \"left double quotation mark\" symbol."
msgid "Left double quotation mark"
-msgstr ""
+msgstr "Ľavá dvojitá uvodzovka"
msgctxt "A label for the \"right double quotation mark\" symbol."
msgid "Right double quotation mark"
-msgstr ""
+msgstr "Pravá dvojitá uvodzovka"
msgctxt "A label for the \"single low-9 quotation mark\" symbol."
msgid "Single low-9 quotation mark"
-msgstr ""
+msgstr "Spodná uvodzovka"
msgctxt "A label for the \"double low-9 quotation mark\" symbol."
msgid "Double low-9 quotation mark"
-msgstr ""
+msgstr "Dvojitá spodná uvodzovka"
msgctxt "A label for the \"inverted exclamation mark\" symbol."
msgid "Inverted exclamation mark"
-msgstr ""
+msgstr "Obrátený výkričník"
msgctxt "A label for the \"inverted question mark\" symbol."
msgid "Inverted question mark"
-msgstr ""
+msgstr "Obrátený otáznik"
msgctxt "A label for the \"two dot leader\" symbol."
msgid "Two dot leader"
-msgstr ""
+msgstr "Horizontálna dvojbodka"
msgctxt "A label for the \"horizontal ellipsis\" symbol."
msgid "Horizontal ellipsis"
-msgstr ""
+msgstr "Trojbodka"
msgctxt "A label for the \"double dagger\" symbol."
msgid "Double dagger"
-msgstr ""
+msgstr "Dvojkríž"
msgctxt "A label for the \"per mille sign\" symbol."
msgid "Per mille sign"
-msgstr ""
+msgstr "Promile"
msgctxt "A label for the \"per ten thousand sign\" symbol."
msgid "Per ten thousand sign"
-msgstr ""
+msgstr "Na desaťtisíc"
msgctxt "A label for the \"double exclamation mark\" symbol."
msgid "Double exclamation mark"
-msgstr ""
+msgstr "Dvojitý výkričník"
msgctxt "A label for the \"question exclamation mark\" symbol."
msgid "Question exclamation mark"
-msgstr ""
+msgstr "Otáznik a výkričník"
msgctxt "A label for the \"exclamation question mark\" symbol."
msgid "Exclamation question mark"
-msgstr ""
+msgstr "Výkričník a otáznik"
msgctxt "A label for the \"double question mark\" symbol."
msgid "Double question mark"
-msgstr ""
+msgstr "Dvojitý otáznik"
msgctxt "A label for the \"copyright sign\" symbol."
msgid "Copyright sign"
-msgstr ""
+msgstr "Copyright"
msgctxt "A label for the \"registered sign\" symbol."
msgid "Registered sign"
-msgstr ""
+msgstr "Registrovaný"
msgctxt "A label for the \"trade mark sign\" symbol."
msgid "Trade mark sign"
-msgstr ""
+msgstr "Ochranná známka"
msgctxt "A label for the \"section sign\" symbol."
msgid "Section sign"
-msgstr ""
+msgstr "Sekcia"
msgctxt "A label for the \"paragraph sign\" symbol."
msgid "Paragraph sign"
-msgstr ""
+msgstr "Odsek"
msgctxt "A label for the \"reversed paragraph sign\" symbol."
msgid "Reversed paragraph sign"
-msgstr ""
+msgstr "Obrátený znak odseku"
msgctxt "A label for a tooltip showing when the user hovers over the plugin icon."
msgid "Character categories"
-msgstr ""
+msgstr "Kategórie znakov"
diff --git a/packages/ckeditor5-table/docs/_snippets/features/table.js b/packages/ckeditor5-table/docs/_snippets/features/table.js
index 99b57acb56d..b5f99d47a76 100644
--- a/packages/ckeditor5-table/docs/_snippets/features/table.js
+++ b/packages/ckeditor5-table/docs/_snippets/features/table.js
@@ -26,7 +26,10 @@ ClassicEditor
target: window.findToolbarItem( editor.ui.view.toolbar,
item => item.buttonView && item.buttonView.label && item.buttonView.label === 'Insert table' ),
text: 'Click to create a table.',
- editor
+ editor,
+ tippyOptions: {
+ placement: 'bottom-start'
+ }
} );
} )
.catch( err => {
diff --git a/packages/ckeditor5-table/docs/features/table.md b/packages/ckeditor5-table/docs/features/table.md
index ae69d287ce5..be1827558a9 100644
--- a/packages/ckeditor5-table/docs/features/table.md
+++ b/packages/ckeditor5-table/docs/features/table.md
@@ -312,6 +312,33 @@ The above model structure will be rendered to the data and to the editing view a
At the moment it is not possible to completely disallow block content in tables. See the [discussion on GitHub](https://github.com/ckeditor/ckeditor5-table/issues/101) about adding a configuration option that would enable that. Add a 👍 if you need this feature.
+## Disallowing nesting tables
+
+By default, the editor allows nesting a table inside another table's cell.
+
+In order to disallow nesting tables you need to register an additional schema rule. It needs to be added before the data gets loaded into the editor, hence it is best to implement it as a plugin:
+
+```js
+function DisallowNestingTables( editor ) {
+ editor.model.schema.addChildCheck( ( context, childDefinition ) => {
+ if ( childDefinition.name == 'table' && Array.from( context.getNames() ).includes( 'table' ) ) {
+ return false;
+ }
+ } );
+}
+
+// Pass it via config.extraPlugins or config.plugins:
+
+ClassicEditor
+ .create( document.querySelector( '#editor' ), {
+ extraPlugins: [ DisallowNestingTables ],
+
+ // The rest of the config.
+ } )
+ .then( ... )
+ .catch( ... );
+```
+
## Common API
### UI components
diff --git a/packages/ckeditor5-table/lang/translations/cs.po b/packages/ckeditor5-table/lang/translations/cs.po
index 13173d5586f..8959a715167 100644
--- a/packages/ckeditor5-table/lang/translations/cs.po
+++ b/packages/ckeditor5-table/lang/translations/cs.po
@@ -134,7 +134,7 @@ msgstr "Pozadí"
msgctxt "The label for the input that allows configuring the padding of a table cell."
msgid "Padding"
-msgstr ""
+msgstr "Vnitřní okraj"
msgctxt "The label describing a group of form fields that allows setting dimensions of a table or a table cell."
msgid "Dimensions"
@@ -150,100 +150,100 @@ msgstr "Zarovnání"
msgctxt "The label used by assistive technologies describing a toolbar that allows configuring the horizontal text alignment in a table cell."
msgid "Horizontal text alignment toolbar"
-msgstr ""
+msgstr "Horizontální zarovnání textu v panelu"
msgctxt "The label used by assistive technologies describing a toolbar that allows configuring the vertical text alignment in a table cell."
msgid "Vertical text alignment toolbar"
-msgstr ""
+msgstr "Vertikální zarovnání textu v panelu"
msgctxt "The label used by assistive technologies describing a toolbar that allows configuring the alignment of a table."
msgid "Table alignment toolbar"
-msgstr ""
+msgstr "Panel zarovnání tabulky"
msgctxt "The label for the border style dropdown when no style is applied to a table or a table cell."
msgid "None"
-msgstr ""
+msgstr "Žádná"
msgctxt "The label for the border style dropdown when the solid border is applied to a table or a table cell."
msgid "Solid"
-msgstr ""
+msgstr "Plná"
msgctxt "The label for the border style dropdown when the dotted border is applied to a table or a table cell."
msgid "Dotted"
-msgstr ""
+msgstr "Tečkovaná"
msgctxt "The label for the border style dropdown when the dashed border is applied to a table or a table cell."
msgid "Dashed"
-msgstr ""
+msgstr "Čárkovaná"
msgctxt "The label for the border style dropdown when the double border is applied to a table or a table cell."
msgid "Double"
-msgstr ""
+msgstr "Dvojitá"
msgctxt "The label for the border style dropdown when the groove border is applied to a table or a table cell."
msgid "Groove"
-msgstr ""
+msgstr "Drážkovaná"
msgctxt "The label for the border style dropdown when the ridge border is applied to a table or a table cell."
msgid "Ridge"
-msgstr ""
+msgstr "Rámovaná"
msgctxt "The label for the border style dropdown when the inset border is applied to a table or a table cell."
msgid "Inset"
-msgstr ""
+msgstr "Vložená zevnitř"
msgctxt "The label for the border style dropdown when the outset border is applied to a table or a table cell."
msgid "Outset"
-msgstr ""
+msgstr "Vložená zvenku"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the left."
msgid "Align cell text to the left"
-msgstr ""
+msgstr "Zarovnat text buňky doleva"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the center."
msgid "Align cell text to the center"
-msgstr ""
+msgstr "Zarovnat text buňky na střed"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the right."
msgid "Align cell text to the right"
-msgstr ""
+msgstr "Zarovnat text buňky doprava"
msgctxt "The label used by assistive technologies describing a button that justifies the table cell text."
msgid "Justify cell text"
-msgstr ""
+msgstr "Zarovnat text buňky z obou stran"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the top."
msgid "Align cell text to the top"
-msgstr ""
+msgstr "Zarovnat text buňky nahoru"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the middle."
msgid "Align cell text to the middle"
-msgstr ""
+msgstr "Zarovnat text buňky na střed"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the bottom."
msgid "Align cell text to the bottom"
-msgstr ""
+msgstr "Zarovnat text buňky dolů"
msgctxt "The label used by assistive technologies describing a button that aligns the table to the left."
msgid "Align table to the left"
-msgstr ""
+msgstr "Zarovnat tabulku doleva"
msgctxt "The label used by assistive technologies describing a button that centers the table."
msgid "Center table"
-msgstr ""
+msgstr "Centrovat tabulku"
msgctxt "The label used by assistive technologies describing a button that aligns the table to the right."
msgid "Align table to the right"
-msgstr ""
+msgstr "Zarovnat tabulku doprava"
msgctxt "The localized error string that can be displayed next to color (background, border) fields that have an invalid value"
msgid "The color is invalid. Try \"#FF0000\" or \"rgb(255,0,0)\" or \"red\"."
-msgstr ""
+msgstr "Barva má nesprávný formát. Zkuste \"#FF0000\", \"rgb(255,0,0)\" nebo \"red\"."
msgctxt "The localized error string that can be displayed next to length (padding, border width) fields that have an invalid value."
msgid "The value is invalid. Try \"10px\" or \"2em\" or simply \"2\"."
-msgstr ""
+msgstr "Hodnota je nesprávná. Zkuste \"10px\", \"2em\" nebo jednoduše \"2\"."
msgctxt "The label used by assistive technologies describing a button that opens a color picker, where user can choose a configured color for a certain properties (eg.: background color, color, border-color etc.)."
msgid "Color picker"
-msgstr ""
+msgstr "Vybrat barvu"
diff --git a/packages/ckeditor5-table/lang/translations/sk.po b/packages/ckeditor5-table/lang/translations/sk.po
index d98d52e2677..1956082b9b4 100644
--- a/packages/ckeditor5-table/lang/translations/sk.po
+++ b/packages/ckeditor5-table/lang/translations/sk.po
@@ -38,7 +38,7 @@ msgstr "Odstrániť stĺpec"
msgctxt "Label for the select the entire table column button."
msgid "Select column"
-msgstr ""
+msgstr "Vybrať stĺpec"
msgctxt "Label for the table column dropdown button."
msgid "Column"
@@ -62,7 +62,7 @@ msgstr "Odstrániť riadok"
msgctxt "Label for the select the entire table row button."
msgid "Select row"
-msgstr ""
+msgstr "Vybrať riadok"
msgctxt "Label for the table row dropdown button."
msgid "Row"
@@ -102,148 +102,148 @@ msgstr "Panel nástrojov tabuľky"
msgctxt "The label describing the form allowing to specify the properties of a selected table."
msgid "Table properties"
-msgstr ""
+msgstr "Vlastnosti tabuľky"
msgctxt "The label describing the form allowing to specify the properties of a selected table cell."
msgid "Cell properties"
-msgstr ""
+msgstr "Vlastnosti bunky"
msgctxt "The label describing a group of border–related form fields (border style, color, etc.)."
msgid "Border"
-msgstr ""
+msgstr "Orámovanie"
msgctxt "The label for the dropdown that allows configuring the border style of a table or a table cell."
msgid "Style"
-msgstr ""
+msgstr "Štýl"
msgctxt "The label for the input that allows configuring the width of a table or a table cell or the width of a border."
msgid "Width"
-msgstr ""
+msgstr "Šírka"
msgctxt "The label for the input that allows configuring the height of a table or a table cell."
msgid "Height"
-msgstr ""
+msgstr "Výška"
msgctxt "The label for the input that allows configuring the border color of a table or a table cell."
msgid "Color"
-msgstr ""
+msgstr "Farba"
msgctxt "The label for the input that allows configuring the background color of a table or a table cell."
msgid "Background"
-msgstr ""
+msgstr "Pozadie"
msgctxt "The label for the input that allows configuring the padding of a table cell."
msgid "Padding"
-msgstr ""
+msgstr "Vnútorný okraj"
msgctxt "The label describing a group of form fields that allows setting dimensions of a table or a table cell."
msgid "Dimensions"
-msgstr ""
+msgstr "Rozmery"
msgctxt "The label for the group of toolbars that allows configuring the text alignment in a table cell."
msgid "Table cell text alignment"
-msgstr ""
+msgstr "Zarovnanie textu v bunke"
msgctxt "The label for the toolbar that allows configuring the alignment of a table."
msgid "Alignment"
-msgstr ""
+msgstr "Zarovnanie"
msgctxt "The label used by assistive technologies describing a toolbar that allows configuring the horizontal text alignment in a table cell."
msgid "Horizontal text alignment toolbar"
-msgstr ""
+msgstr "Horizontálne zarovnanie textu v panely"
msgctxt "The label used by assistive technologies describing a toolbar that allows configuring the vertical text alignment in a table cell."
msgid "Vertical text alignment toolbar"
-msgstr ""
+msgstr "Vertikálne zarovnanie textu v panely"
msgctxt "The label used by assistive technologies describing a toolbar that allows configuring the alignment of a table."
msgid "Table alignment toolbar"
-msgstr ""
+msgstr "Panel zarovnania tabuľky"
msgctxt "The label for the border style dropdown when no style is applied to a table or a table cell."
msgid "None"
-msgstr ""
+msgstr "Žiadna"
msgctxt "The label for the border style dropdown when the solid border is applied to a table or a table cell."
msgid "Solid"
-msgstr ""
+msgstr "Plná"
msgctxt "The label for the border style dropdown when the dotted border is applied to a table or a table cell."
msgid "Dotted"
-msgstr ""
+msgstr "Bodkovaná"
msgctxt "The label for the border style dropdown when the dashed border is applied to a table or a table cell."
msgid "Dashed"
-msgstr ""
+msgstr "Čiarkovaná"
msgctxt "The label for the border style dropdown when the double border is applied to a table or a table cell."
msgid "Double"
-msgstr ""
+msgstr "Dvojitá"
msgctxt "The label for the border style dropdown when the groove border is applied to a table or a table cell."
msgid "Groove"
-msgstr ""
+msgstr "Drážkovaná"
msgctxt "The label for the border style dropdown when the ridge border is applied to a table or a table cell."
msgid "Ridge"
-msgstr ""
+msgstr "Rámovaná"
msgctxt "The label for the border style dropdown when the inset border is applied to a table or a table cell."
msgid "Inset"
-msgstr ""
+msgstr "Vložená z vnútra"
msgctxt "The label for the border style dropdown when the outset border is applied to a table or a table cell."
msgid "Outset"
-msgstr ""
+msgstr "Vložená zvonku"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the left."
msgid "Align cell text to the left"
-msgstr ""
+msgstr "Zarovnať text bunky doľava"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the center."
msgid "Align cell text to the center"
-msgstr ""
+msgstr "Zarovnať text bunky na stred"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the right."
msgid "Align cell text to the right"
-msgstr ""
+msgstr "Zarovnať text bunky doprava"
msgctxt "The label used by assistive technologies describing a button that justifies the table cell text."
msgid "Justify cell text"
-msgstr ""
+msgstr "Zarovnať text bunky z oboch strán"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the top."
msgid "Align cell text to the top"
-msgstr ""
+msgstr "Zarovnať text bunky nahor"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the middle."
msgid "Align cell text to the middle"
-msgstr ""
+msgstr "Zarovnať text bunky na stred"
msgctxt "The label used by assistive technologies describing a button that aligns the table cell text to the bottom."
msgid "Align cell text to the bottom"
-msgstr ""
+msgstr "Zarovnať text bunky nadol"
msgctxt "The label used by assistive technologies describing a button that aligns the table to the left."
msgid "Align table to the left"
-msgstr ""
+msgstr "Zarovnať tabuľku doľava"
msgctxt "The label used by assistive technologies describing a button that centers the table."
msgid "Center table"
-msgstr ""
+msgstr "Centrovať tabuľku"
msgctxt "The label used by assistive technologies describing a button that aligns the table to the right."
msgid "Align table to the right"
-msgstr ""
+msgstr "Zarovnať tabuľku doprava"
msgctxt "The localized error string that can be displayed next to color (background, border) fields that have an invalid value"
msgid "The color is invalid. Try \"#FF0000\" or \"rgb(255,0,0)\" or \"red\"."
-msgstr ""
+msgstr "Farba má nesprávny formát. Skúste \"#FF0000\", \"rgb(255,0,0)\" alebo \"red\"."
msgctxt "The localized error string that can be displayed next to length (padding, border width) fields that have an invalid value."
msgid "The value is invalid. Try \"10px\" or \"2em\" or simply \"2\"."
-msgstr ""
+msgstr "Hodnota je nesprávna. Skúste \"10px\", \"2em\" alebo jednoducho \"2\"."
msgctxt "The label used by assistive technologies describing a button that opens a color picker, where user can choose a configured color for a certain properties (eg.: background color, color, border-color etc.)."
msgid "Color picker"
-msgstr ""
+msgstr "Vybrať farbu"
diff --git a/packages/ckeditor5-table/src/tableediting.js b/packages/ckeditor5-table/src/tableediting.js
index 3ab052b87ab..3c83f87e07f 100644
--- a/packages/ckeditor5-table/src/tableediting.js
+++ b/packages/ckeditor5-table/src/tableediting.js
@@ -84,13 +84,6 @@ export default class TableEditing extends Plugin {
// Allow all $block content inside a table cell.
schema.extend( '$block', { allowIn: 'tableCell' } );
- // Disallow a table in a table.
- schema.addChildCheck( ( context, childDefinition ) => {
- if ( childDefinition.name == 'table' && Array.from( context.getNames() ).includes( 'table' ) ) {
- return false;
- }
- } );
-
// Figure conversion.
conversion.for( 'upcast' ).add( upcastTableFigure() );
diff --git a/packages/ckeditor5-table/tests/commands/inserttablecommand.js b/packages/ckeditor5-table/tests/commands/inserttablecommand.js
index 3f4bb0841de..45d61c50adc 100644
--- a/packages/ckeditor5-table/tests/commands/inserttablecommand.js
+++ b/packages/ckeditor5-table/tests/commands/inserttablecommand.js
@@ -44,9 +44,9 @@ describe( 'InsertTableCommand', () => {
expect( command.isEnabled ).to.be.true;
} );
- it( 'should be false if in table', () => {
+ it( 'should be true if in table', () => {
setData( model, ' ' +
- 'foo[]
' );
- expect( command.isEnabled ).to.be.false;
+ expect( command.isEnabled ).to.be.true;
} );
} );
diff --git a/packages/ckeditor5-table/tests/converters/upcasttable.js b/packages/ckeditor5-table/tests/converters/upcasttable.js
index ffdc7ad6b75..bdfc571e44a 100644
--- a/packages/ckeditor5-table/tests/converters/upcasttable.js
+++ b/packages/ckeditor5-table/tests/converters/upcasttable.js
@@ -326,14 +326,15 @@ describe( 'upcastTable()', () => {
);
} );
- it( 'should strip table in table', () => {
+ it( 'should not strip table in table', () => {
editor.setData(
'' +
'' +
+ 'foo ' +
'' +
'' +
'' +
- 'tableception ' +
+ 'bar ' +
' ' +
'
' +
' ' +
@@ -345,7 +346,52 @@ describe( 'upcastTable()', () => {
'' +
'' +
'' +
- 'tableception ' +
+ 'foo ' +
+ ' ' +
+ '' +
+ '' +
+ '' +
+ '' +
+ 'bar ' +
+ ' ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ '
'
+ );
+ } );
+
+ it( 'should strip table in table if nested tables are forbidden', () => {
+ model.schema.addChildCheck( ( context, childDefinition ) => {
+ if ( childDefinition.name == 'table' && Array.from( context.getNames() ).includes( 'table' ) ) {
+ return false;
+ }
+ } );
+
+ editor.setData(
+ '' +
+ '' +
+ 'foo ' +
+ '' +
+ '' +
+ '' +
+ 'bar ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ '
'
+ );
+
+ expectModel(
+ '' +
+ '' +
+ '' +
+ 'foo ' +
+ ' ' +
+ '' +
+ 'bar ' +
' ' +
' ' +
'
'
diff --git a/packages/ckeditor5-table/tests/manual/table.html b/packages/ckeditor5-table/tests/manual/table.html
index feae0e74bc8..88d8a6442fa 100644
--- a/packages/ckeditor5-table/tests/manual/table.html
+++ b/packages/ckeditor5-table/tests/manual/table.html
@@ -8,7 +8,7 @@
Complex table:
-
+
+ Nested tables
+
+
+
+
+
+
+
+
+
+
+ Paragraph before table
+
+
+
+ Nested table
+
+
+
+ Paragraph after table
+
+
+
+
+
+
+
+
+
diff --git a/packages/ckeditor5-table/tests/manual/table.md b/packages/ckeditor5-table/tests/manual/table.md
index c8e22f85897..fe2ed7f00d1 100644
--- a/packages/ckeditor5-table/tests/manual/table.md
+++ b/packages/ckeditor5-table/tests/manual/table.md
@@ -67,3 +67,13 @@ Merging cells:
Splitting cells:
1. Split not merged cell vertically/horizontally.
2. Split already merged cell vertically/horizontally.
+
+Nested tables:
+1. Verify that nested tables are displayed correctly and do not throw an error.
+2. Verify that it is possible to insert new table inside existing table cell.
+3. Verify that it is possible to copy & paste nested table below the existing one (in the editor root).
+4. Verify that it is possible to copy & paste nested table inside other table cell.
+ * Copying & pasting a table only (in the clipboard there is only a table with no siblings) should merge content of this table with the nearest ancestor table. In other words: pasted table is not inserted as a table (as a widget) in table cell, but it is merged with the first found table ancestor.
+ * Copying & pasting a table with any sibling (e.g. a paragraph) should paste exactly what has been copied.
+5. Hover the outer table with the mouse cursor. Verify that the yellow border and selection handle on the inner table is not visible (they should be visible only for the hovered table).
+6. Select the outer table. Verify that the blue selection handle on the inner table is not visible (it should be visible only for the selected table).
diff --git a/packages/ckeditor5-table/tests/manual/tablenonesting.html b/packages/ckeditor5-table/tests/manual/tablenonesting.html
new file mode 100644
index 00000000000..416b045eda3
--- /dev/null
+++ b/packages/ckeditor5-table/tests/manual/tablenonesting.html
@@ -0,0 +1,42 @@
+
+
+
+ Nested tables (in the data):
+
+
+
+
+
+
+
+
+
+
+
+ Paragraph before table
+
+
+
+
+ Nested table (that should be unnested on data load).
+
+
+
+
+ Paragraph after table
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/ckeditor5-table/tests/manual/tablenonesting.js b/packages/ckeditor5-table/tests/manual/tablenonesting.js
new file mode 100644
index 00000000000..3298dd61cae
--- /dev/null
+++ b/packages/ckeditor5-table/tests/manual/tablenonesting.js
@@ -0,0 +1,35 @@
+/**
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* globals console, window, document */
+
+import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor';
+import ArticlePluginSet from '@ckeditor/ckeditor5-core/tests/_utils/articlepluginset';
+
+function DisallowNestingTables( editor ) {
+ editor.model.schema.addChildCheck( ( context, childDefinition ) => {
+ if ( childDefinition.name == 'table' && Array.from( context.getNames() ).includes( 'table' ) ) {
+ return false;
+ }
+ } );
+}
+
+ClassicEditor
+ .create( document.querySelector( '#editor' ), {
+ plugins: [ ArticlePluginSet, DisallowNestingTables ],
+ toolbar: [
+ 'heading', '|', 'insertTable', '|', 'bold', 'italic', 'bulletedList', 'numberedList', 'blockQuote', 'undo', 'redo'
+ ],
+ table: {
+ contentToolbar: [ 'tableColumn', 'tableRow', 'mergeTableCells' ],
+ tableToolbar: [ 'bold', 'italic' ]
+ }
+ } )
+ .then( editor => {
+ window.editor = editor;
+ } )
+ .catch( err => {
+ console.error( err.stack );
+ } );
diff --git a/packages/ckeditor5-table/tests/manual/tablenonesting.md b/packages/ckeditor5-table/tests/manual/tablenonesting.md
new file mode 100644
index 00000000000..8846563d6ba
--- /dev/null
+++ b/packages/ckeditor5-table/tests/manual/tablenonesting.md
@@ -0,0 +1,2 @@
+* The nested table present in the data should be "unnested" on data load. Its content should be inlined in the outermost table.
+* It should not be possible to insert a table into a table in any way (UI, paste, d&d, etc.).
\ No newline at end of file
diff --git a/packages/ckeditor5-table/tests/manual/tableproperties.html b/packages/ckeditor5-table/tests/manual/tableproperties.html
index 1bbffed5d03..1affb8f30fd 100644
--- a/packages/ckeditor5-table/tests/manual/tableproperties.html
+++ b/packages/ckeditor5-table/tests/manual/tableproperties.html
@@ -23,6 +23,33 @@
B2
C3
+
+
+
+
+
+ Column X
+ Column Y
+ Column Z
+
+
+
+
+
+ X1
+
+ Y1
+ Z1
+
+
+ X2
+ Y2
+ Z2
+
+
+
+
+
diff --git a/packages/ckeditor5-table/tests/manual/tableproperties.md b/packages/ckeditor5-table/tests/manual/tableproperties.md
index 7b9976425dd..fb5ab40b322 100644
--- a/packages/ckeditor5-table/tests/manual/tableproperties.md
+++ b/packages/ckeditor5-table/tests/manual/tableproperties.md
@@ -2,7 +2,7 @@
The editor should be loaded with tables:
-1. A table with various table styles set.
+1. A table with various table styles set and with nested styled table in the last row.
1. A "Table from Word" sample.
1. A "Table from GDocs" sample.
1. A "Table from GDocs" sample that has one cell with many styles set.
diff --git a/packages/ckeditor5-table/tests/tableclipboard-paste.js b/packages/ckeditor5-table/tests/tableclipboard-paste.js
index 275c525820c..877e1f4bde0 100644
--- a/packages/ckeditor5-table/tests/tableclipboard-paste.js
+++ b/packages/ckeditor5-table/tests/tableclipboard-paste.js
@@ -117,8 +117,13 @@ describe( 'table clipboard', () => {
model.insertContent( tableToInsert, selectedTableCells );
} );
+ const tableModelData = modelTable( [
+ [ 'foo', 'foo' ],
+ [ 'foo', 'foo' ]
+ ] );
+
assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
- [ '', '', '02', '03' ],
+ [ tableModelData, '', '02', '03' ],
[ '', '', '12', '13' ],
[ '20', '21', '22', '23' ],
[ '30', '31', '32', '33' ]
@@ -223,21 +228,24 @@ describe( 'table clipboard', () => {
modelRoot.getNodeByPath( [ 0, 1, 1 ] )
);
- const table = viewTable( [
+ const tableToInsert = [
[ 'aa', 'ab' ],
[ 'ba', 'bb' ]
- ] );
+ ];
+
+ const tableViewData = viewTable( tableToInsert );
+ const tableModelData = modelTable( tableToInsert );
const data = {
dataTransfer: createDataTransfer(),
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
- data.dataTransfer.setData( 'text/html', `${ table }foo
` );
+ data.dataTransfer.setData( 'text/html', `${ tableViewData }foo
` );
viewDocument.fire( 'paste', data );
assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
- [ 'foo', '', '02', '03' ],
+ [ tableModelData + 'foo ', '', '02', '03' ],
[ '', '', '12', '13' ],
[ '20', '21', '22', '23' ],
[ '30', '31', '32', '33' ]
@@ -250,21 +258,24 @@ describe( 'table clipboard', () => {
modelRoot.getNodeByPath( [ 0, 1, 1 ] )
);
- const table = viewTable( [
+ const tableToInsert = [
[ 'aa', 'ab' ],
[ 'ba', 'bb' ]
- ] );
+ ];
+
+ const tableViewData = viewTable( tableToInsert );
+ const tableModelData = modelTable( tableToInsert );
const data = {
dataTransfer: createDataTransfer(),
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
- data.dataTransfer.setData( 'text/html', `foo
${ table }` );
+ data.dataTransfer.setData( 'text/html', `foo
${ tableViewData }` );
viewDocument.fire( 'paste', data );
assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
- [ 'foo', '', '02', '03' ],
+ [ 'foo ' + tableModelData, '', '02', '03' ],
[ '', '', '12', '13' ],
[ '20', '21', '22', '23' ],
[ '30', '31', '32', '33' ]
@@ -277,21 +288,24 @@ describe( 'table clipboard', () => {
modelRoot.getNodeByPath( [ 0, 1, 1 ] )
);
- const table = viewTable( [
+ const tableToInsert = [
[ 'aa', 'ab' ],
[ 'ba', 'bb' ]
- ] );
+ ];
+
+ const tableViewData = viewTable( tableToInsert );
+ const tableModelData = modelTable( tableToInsert );
const data = {
dataTransfer: createDataTransfer(),
preventDefault: sinon.spy(),
stopPropagation: sinon.spy()
};
- data.dataTransfer.setData( 'text/html', `${ table }${ table }` );
+ data.dataTransfer.setData( 'text/html', `${ tableViewData }${ tableViewData }` );
viewDocument.fire( 'paste', data );
assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
- [ '', '', '02', '03' ],
+ [ tableModelData + tableModelData, '', '02', '03' ],
[ '', '', '12', '13' ],
[ '20', '21', '22', '23' ],
[ '30', '31', '32', '33' ]
@@ -3942,9 +3956,13 @@ describe( 'table clipboard', () => {
] ) );
} );
- it( 'should not blow up when pasting unsupported element in table', async () => {
+ it( 'should allow pasting table inside table cell with style', async () => {
await createEditor( [ TableCellPropertiesEditing ] );
+ const color = 'rgb(242, 242, 242)';
+ const style = 'solid';
+ const width = '2px';
+
pasteHtml( editor,
'' +
'' +
@@ -3954,7 +3972,7 @@ describe( 'table clipboard', () => {
'' +
'' +
'' +
- '' +
+ ` ` +
'Test
' +
' ' +
' ' +
@@ -3967,8 +3985,17 @@ describe( 'table clipboard', () => {
'
'
);
+ const tableModelData = modelTable( [
+ [ {
+ contents: 'Test ',
+ borderColor: `{"top":"${ color }","bottom":"${ color }","right":"${ color }","left":"${ color }"}`,
+ borderStyle: `{"top":"${ style }","bottom":"${ style }","right":"${ style }","left":"${ style }"}`,
+ borderWidth: `{"top":"${ width }","bottom":"${ width }","right":"${ width }","left":"${ width }"}`
+ } ]
+ ] );
+
assertEqualMarkup( getModelData( model, { withoutSelection: true } ), modelTable( [
- [ 'Test ' ]
+ [ tableModelData ]
] ) );
} );
} );
diff --git a/packages/ckeditor5-table/tests/tableediting.js b/packages/ckeditor5-table/tests/tableediting.js
index c48829db02f..d6e6361e0ce 100644
--- a/packages/ckeditor5-table/tests/tableediting.js
+++ b/packages/ckeditor5-table/tests/tableediting.js
@@ -81,7 +81,7 @@ describe( 'TableEditing', () => {
// Table cell contents:
expect( model.schema.checkChild( [ '$root', 'table', 'tableRow', 'tableCell' ], '$text' ) ).to.be.false;
expect( model.schema.checkChild( [ '$root', 'table', 'tableRow', 'tableCell' ], '$block' ) ).to.be.true;
- expect( model.schema.checkChild( [ '$root', 'table', 'tableRow', 'tableCell' ], 'table' ) ).to.be.false;
+ expect( model.schema.checkChild( [ '$root', 'table', 'tableRow', 'tableCell' ], 'table' ) ).to.be.true;
expect( model.schema.checkChild( [ '$root', 'table', 'tableRow', 'tableCell' ], 'image' ) ).to.be.true;
} );
diff --git a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
index d81e4ac477c..98646113738 100644
--- a/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
+++ b/packages/ckeditor5-table/tests/tableproperties/tablepropertiesediting.js
@@ -245,9 +245,19 @@ describe( 'table properties', () => {
'' +
'' +
'' +
- '' +
- 'child:00' +
- ' ' +
+ '' +
+ '' +
+ '' +
+ '' +
+ 'child:00' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' +
' ' +
'' +
'
]'
@@ -280,7 +290,13 @@ describe( 'table properties', () => {
'[' +
'' +
'' +
- ' ' +
+ '' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' +
' ' +
' ' +
'
]'
@@ -307,7 +323,17 @@ describe( 'table properties', () => {
'[' +
'' +
'' +
- ' ' +
+ '' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
' +
' ' +
' ' +
'
]'
@@ -348,12 +374,174 @@ describe( 'table properties', () => {
'parent:00 ' +
'' +
'' +
- 'child:00 ' +
+ '' +
+ '' +
+ '' +
+ 'child:00 ' +
+ ' ' +
+ ' ' +
+ '
' +
' ' +
'' +
']'
);
} );
+
+ describe( 'nested tables forbidden by custom rule', () => {
+ // Nested tables are supported since https://github.com/ckeditor/ckeditor5/issues/3232, so let's check
+ // if the editor will not blow up in case nested tables are forbidden by custom scheme rule.
+ beforeEach( () => {
+ model.schema.addChildCheck( ( context, childDefinition ) => {
+ if ( childDefinition.name == 'table' && Array.from( context.getNames() ).includes( 'table' ) ) {
+ return false;
+ }
+ } );
+ } );
+
+ it( 'should upcast tables with nested tables in their cells', () => {
+ editor.setData(
+ '' +
+ '' +
+ 'parent:00 ' +
+ '' +
+ 'child:00
' +
+ ' ' +
+ ' ' +
+ '
'
+ );
+
+ const table = model.document.getRoot().getNodeByPath( [ 0 ] );
+
+ assertTRBLAttribute( table, 'borderColor', 'red' );
+ assertTRBLAttribute( table, 'borderStyle', 'solid' );
+ assertTRBLAttribute( table, 'borderWidth', '1px' );
+
+ expect( getModelData( editor.model ) ).to.equal(
+ '[' +
+ '' +
+ '' +
+ '' +
+ 'parent:00' +
+ ' ' +
+ ' ' +
+ '' +
+ '' +
+ 'child:00' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
]'
+ );
+ } );
+
+ // https://github.com/ckeditor/ckeditor5/issues/8393.
+ it( 'should not throw error - inner cell with border style', () => {
+ expect( () => {
+ editor.setData(
+ '' +
+ '' +
+ '' +
+ ' ' +
+ '' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ '' +
+ '
' +
+ ' ' +
+ ' ' +
+ '' +
+ '
'
+ );
+ } ).not.to.throw();
+
+ expect( getModelData( editor.model ) ).to.equal(
+ '[' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
]'
+ );
+ } );
+
+ // https://github.com/ckeditor/ckeditor5/issues/8393.
+ it( 'should not throw error - inner empty table with border style', () => {
+ expect( () => {
+ editor.setData(
+ '' +
+ '' +
+ '' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ '' +
+ '
'
+ );
+ } ).not.to.throw();
+
+ expect( getModelData( editor.model ) ).to.equal(
+ '[' +
+ '' +
+ '' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '
]'
+ );
+ } );
+
+ // https://github.com/ckeditor/ckeditor5/issues/8393.
+ it( 'should not throw error - no tables allowed in an element', () => {
+ // Conversion will create a merged text node out of all the text contents,
+ // including the one in elements not allowed by schema in this scope.
+ // Let's make sure that upcasting will not try to use model that got processed this way.
+ expect( () => {
+ editor.setData(
+ '' +
+ '' +
+ '' +
+ '' +
+ '' +
+ 'parent:00 ' +
+ '' +
+ '' +
+ '' +
+ 'child:00 ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ' ' +
+ '
' +
+ ' ' +
+ ''
+ );
+ } ).not.to.throw();
+
+ expect( getModelData( editor.model ) ).to.equal(
+ '[' +
+ '' +
+ '' +
+ 'parent:00 ' +
+ ' ' +
+ '' +
+ 'child:00 ' +
+ ' ' +
+ ' ' +
+ '
]'
+ );
+ } );
+ } );
} );
} );
diff --git a/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widget.css b/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widget.css
index de1a25b1c15..747d5d14938 100644
--- a/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widget.css
+++ b/packages/ckeditor5-theme-lark/theme/ckeditor5-widget/widget.css
@@ -94,16 +94,16 @@
}
}
- /* Show the selection handler on mouse hover over the widget. */
- &:hover .ck-widget__selection-handle {
+ /* Show the selection handler on mouse hover over the widget, but not for nested widgets. */
+ &:hover > .ck-widget__selection-handle {
opacity: 1;
background-color: var(--ck-color-widget-hover-border);
}
- /* Show the selection handler when the widget is selected. */
+ /* Show the selection handler when the widget is selected, but not for nested widgets. */
&.ck-widget_selected,
&.ck-widget_selected:hover {
- & .ck-widget__selection-handle {
+ & > .ck-widget__selection-handle {
opacity: 1;
background-color: var(--ck-color-focus-border);
@@ -153,8 +153,8 @@
outline-color: var(--ck-color-widget-blurred-border);
&.ck-widget_with-selection-handle {
- & .ck-widget__selection-handle,
- & .ck-widget__selection-handle:hover {
+ & > .ck-widget__selection-handle,
+ & > .ck-widget__selection-handle:hover {
background: var(--ck-color-widget-blurred-border);
}
}
diff --git a/packages/ckeditor5-upload/src/adapters/simpleuploadadapter.js b/packages/ckeditor5-upload/src/adapters/simpleuploadadapter.js
index df9c03c7657..7cf4bff17a1 100644
--- a/packages/ckeditor5-upload/src/adapters/simpleuploadadapter.js
+++ b/packages/ckeditor5-upload/src/adapters/simpleuploadadapter.js
@@ -174,7 +174,14 @@ class Adapter {
return reject( response && response.error && response.error.message ? response.error.message : genericErrorText );
}
- resolve( response.url ? { default: response.url } : response.urls );
+ const urls = response.url ? { default: response.url } : response.urls;
+
+ // Resolve with the normalized `urls` property and pass the rest of the response
+ // to allow customizing the behavior of features relying on the upload adapters.
+ resolve( {
+ ...response,
+ urls
+ } );
} );
// Upload progress when it is supported.
diff --git a/packages/ckeditor5-upload/src/filerepository.js b/packages/ckeditor5-upload/src/filerepository.js
index 611ec21bc99..71a32a8a36f 100644
--- a/packages/ckeditor5-upload/src/filerepository.js
+++ b/packages/ckeditor5-upload/src/filerepository.js
@@ -609,6 +609,20 @@ mix( FileLoader, ObservableMixin );
* '1052': 'http://server/default-size.image.png'
* }
*
+ * You can also pass additional properties from the server. In this case you need to wrap URLs
+ * in the `urls` object and pass additional properties along the `urls` property.
+ *
+ * {
+ * myCustomProperty: 'foo',
+ * urls: {
+ * default: 'http://server/default-size.image.png',
+ * '160': 'http://server/size-160.image.png',
+ * '500': 'http://server/size-500.image.png',
+ * '1000': 'http://server/size-1000.image.png',
+ * '1052': 'http://server/default-size.image.png'
+ * }
+ * }
+ *
* NOTE: When returning multiple images, the widest returned one should equal the default one. It is essential to
* correctly set `width` attribute of the image. See this discussion:
* https://github.com/ckeditor/ckeditor5-easy-image/issues/4 for more information.
diff --git a/packages/ckeditor5-upload/tests/adapters/simpleuploadadapter.js b/packages/ckeditor5-upload/tests/adapters/simpleuploadadapter.js
index 3b74b33024c..8d25a6d5454 100644
--- a/packages/ckeditor5-upload/tests/adapters/simpleuploadadapter.js
+++ b/packages/ckeditor5-upload/tests/adapters/simpleuploadadapter.js
@@ -158,8 +158,12 @@ describe( 'SimpleUploadAdapter', () => {
return uploadPromise;
} )
.then( uploadResponse => {
- expect( uploadResponse ).to.be.a( 'object' );
- expect( uploadResponse ).to.have.property( 'default', 'http://example.com/images/image.jpeg' );
+ expect( uploadResponse ).to.deep.equal( {
+ url: 'http://example.com/images/image.jpeg',
+ urls: {
+ default: 'http://example.com/images/image.jpeg'
+ }
+ } );
} );
} );
@@ -200,8 +204,12 @@ describe( 'SimpleUploadAdapter', () => {
return uploadPromise;
} )
.then( uploadResponse => {
- expect( uploadResponse ).to.be.a( 'object' );
- expect( uploadResponse ).to.have.property( 'default', 'http://example.com/images/image.jpeg' );
+ expect( uploadResponse ).to.deep.equal( {
+ url: 'http://example.com/images/image.jpeg',
+ urls: {
+ default: 'http://example.com/images/image.jpeg'
+ }
+ } );
editorElement.remove();
} )
@@ -241,8 +249,12 @@ describe( 'SimpleUploadAdapter', () => {
return uploadPromise;
} )
.then( uploadResponse => {
- expect( uploadResponse ).to.be.a( 'object' );
- expect( uploadResponse ).to.have.property( 'default', 'http://example.com/images/image.jpeg' );
+ expect( uploadResponse ).to.deep.equal( {
+ url: 'http://example.com/images/image.jpeg',
+ urls: {
+ default: 'http://example.com/images/image.jpeg'
+ }
+ } );
editorElement.remove();
} )
@@ -280,8 +292,12 @@ describe( 'SimpleUploadAdapter', () => {
return uploadPromise;
} )
.then( uploadResponse => {
- expect( uploadResponse ).to.be.a( 'object' );
- expect( uploadResponse ).to.have.property( 'default', 'http://example.com/images/image.jpeg' );
+ expect( uploadResponse ).to.deep.equal( {
+ url: 'http://example.com/images/image.jpeg',
+ urls: {
+ default: 'http://example.com/images/image.jpeg'
+ }
+ } );
editorElement.remove();
} )
@@ -318,8 +334,12 @@ describe( 'SimpleUploadAdapter', () => {
return uploadPromise;
} )
.then( uploadResponse => {
- expect( uploadResponse ).to.be.a( 'object' );
- expect( uploadResponse ).to.have.property( 'default', 'http://example.com/images/image.jpeg' );
+ expect( uploadResponse ).to.deep.equal( {
+ url: 'http://example.com/images/image.jpeg',
+ urls: {
+ default: 'http://example.com/images/image.jpeg'
+ }
+ } );
editorElement.remove();
} )
@@ -348,7 +368,32 @@ describe( 'SimpleUploadAdapter', () => {
return uploadPromise;
} )
.then( uploadResponse => {
- expect( uploadResponse ).to.deep.equal( validResponse.urls );
+ expect( uploadResponse ).to.deep.equal( validResponse );
+ } );
+ } );
+
+ it( 'should pass additional properties to the response', () => {
+ const validResponse = {
+ urls: {
+ default: 'http://example.com/images/image.jpeg',
+ '120': 'http://example.com/images/image-120.jpeg',
+ '240': 'http://example.com/images/image-240.jpeg'
+ },
+ customProperty: 'foo'
+ };
+
+ const uploadPromise = adapter.upload();
+
+ return loader.file
+ .then( () => {
+ sinonXHR.requests[ 0 ].respond( 200, {
+ 'Content-Type': 'application/json'
+ }, JSON.stringify( validResponse ) );
+
+ return uploadPromise;
+ } )
+ .then( uploadResponse => {
+ expect( uploadResponse ).to.deep.equal( validResponse );
} );
} );
diff --git a/packages/ckeditor5-widget/lang/translations/cs.po b/packages/ckeditor5-widget/lang/translations/cs.po
index 3dd522f6ad4..9e67153b451 100644
--- a/packages/ckeditor5-widget/lang/translations/cs.po
+++ b/packages/ckeditor5-widget/lang/translations/cs.po
@@ -22,8 +22,8 @@ msgstr "Panel nástrojů ovládacího prvku"
msgctxt "The title displayed when a mouse is over a button that inserts a paragraph before a block."
msgid "Insert paragraph before block"
-msgstr ""
+msgstr "Vložte odstavec před blok"
msgctxt "The title displayed when a mouse is over a button that inserts a paragraph after a block."
msgid "Insert paragraph after block"
-msgstr ""
+msgstr "Vložte odstavec za blok"
diff --git a/packages/ckeditor5-widget/tests/widget.js b/packages/ckeditor5-widget/tests/widget.js
index c5dd70576e7..4ba2819ee09 100644
--- a/packages/ckeditor5-widget/tests/widget.js
+++ b/packages/ckeditor5-widget/tests/widget.js
@@ -3,7 +3,7 @@
* For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
*/
-/* global document */
+/* global document, window */
import ClassicTestEditor from '@ckeditor/ckeditor5-core/tests/_utils/classictesteditor';
import Enter from '@ckeditor/ckeditor5-enter/src/enter';
@@ -1538,5 +1538,30 @@ describe( 'Widget', () => {
' '
);
} );
+
+ it( 'should show the selection handle only for selected widget if widgets are nested', () => {
+ setModelData( model, ' ' );
+
+ // The top-outer widget.
+ const viewWidgetSelectionHandle = viewDocument.getRoot().getChild( 0 );
+
+ const target = view.domConverter.mapViewToDom( viewWidgetSelectionHandle );
+
+ const domEventDataMock = new DomEventData( view, {
+ target,
+ preventDefault: sinon.spy()
+ } );
+
+ viewDocument.fire( 'mousedown', domEventDataMock );
+
+ // Get all selection handles for all widgets nested inside the top-most one.
+ const selectionHandles = target.querySelectorAll( ':scope .ck-widget .ck-widget__selection-handle' );
+
+ for ( const selectionHandle of selectionHandles ) {
+ const opacity = window.getComputedStyle( selectionHandle ).getPropertyValue( 'opacity' );
+
+ expect( opacity ).to.equal( '0' );
+ }
+ } );
} );
} );
diff --git a/packages/ckeditor5-widget/theme/widget.css b/packages/ckeditor5-widget/theme/widget.css
index ec001d6f09f..fe0251d86df 100644
--- a/packages/ckeditor5-widget/theme/widget.css
+++ b/packages/ckeditor5-widget/theme/widget.css
@@ -31,15 +31,13 @@
}
}
- /* Show the selection handle on mouse hover over the widget. */
- &:hover {
- & .ck-widget__selection-handle {
- visibility: visible;
- }
+ /* Show the selection handle on mouse hover over the widget, but not for nested widgets. */
+ &:hover > .ck-widget__selection-handle {
+ visibility: visible;
}
- /* Show the selection handle when the widget is selected. */
- &.ck-widget_selected .ck-widget__selection-handle {
+ /* Show the selection handle when the widget is selected, but not for nested widgets. */
+ &.ck-widget_selected > .ck-widget__selection-handle {
visibility: visible;
}
}
diff --git a/scripts/continuous-integration-script.js b/scripts/ci/check-packages-code-coverage.js
similarity index 86%
rename from scripts/continuous-integration-script.js
rename to scripts/ci/check-packages-code-coverage.js
index 9853d7881c6..300cdf5d189 100644
--- a/scripts/continuous-integration-script.js
+++ b/scripts/ci/check-packages-code-coverage.js
@@ -9,6 +9,12 @@
'use strict';
+/**
+ * This script should be used on Travis CI. It executes tests and prepares the code coverage report
+ * for each package found in the `packages/` directory. Then, all reports are merged into a single
+ * file that will be sent to Coveralls.
+ */
+
const childProcess = require( 'child_process' );
const crypto = require( 'crypto' );
const fs = require( 'fs' );
@@ -64,7 +70,7 @@ childProcess.execSync( 'mkdir .nyc_output' );
childProcess.execSync( 'rm -r -f .out' );
childProcess.execSync( 'mkdir .out' );
-const packages = childProcess.execSync( 'ls packages -1', {
+const packages = childProcess.execSync( 'ls -1 packages', {
encoding: 'utf8'
} ).toString().trim().split( '\n' );
@@ -115,12 +121,12 @@ if ( Object.values( failedChecks ).some( checksSet => checksSet.size > 0 ) ) {
process.exit( 1 ); // Exit code 1 will break the CI build.
}
-/*
- * @param {String} binaryName - Name of a CLI binary to be called.
- * @param {String[]} cliArguments - An array of arguments to be passed to the `binaryName`.
- * @param {String} packageName - Checked package name.
- * @param {String} checkName - A key associated with the problem in the `failedChecks` dictionary.
- * @param {String} failMessage - Message to be shown if check failed.
+/**
+ * @param {String} binaryName Name of a CLI binary to be called.
+ * @param {Array.} cliArguments An array of arguments to be passed to the `binaryName`.
+ * @param {String} packageName Checked package name.
+ * @param {String} checkName A key associated with the problem in the `failedChecks` dictionary.
+ * @param {String} failMessage Message to be shown if check failed.
*/
function runSubprocess( binaryName, cliArguments, packageName, checkName, failMessage ) {
const subprocess = childProcess.spawnSync( binaryName, cliArguments, {
@@ -155,10 +161,9 @@ function appendCoverageReport() {
matches.forEach( filePath => {
const buffer = fs.readFileSync( filePath );
+ const reportPath = [ '.out', 'combined_lcov.info' ].join( path.sep );
- fs.writeFileSync( [ '.out', 'combined_lcov.info' ].join( path.sep ), buffer, {
- flag: 'as'
- } );
+ fs.writeFileSync( reportPath, buffer, { flag: 'as' } );
} );
}
diff --git a/scripts/ci/travis-check.js b/scripts/ci/travis-check.js
new file mode 100755
index 00000000000..4aa5dc7b5f5
--- /dev/null
+++ b/scripts/ci/travis-check.js
@@ -0,0 +1,115 @@
+#!/usr/bin/env node
+
+/**
+ * @license Copyright (c) 2003-2021, CKSource - Frederico Knabben. All rights reserved.
+ * For licensing, see LICENSE.md or https://ckeditor.com/legal/ckeditor-oss-license
+ */
+
+/* eslint-env node */
+
+'use strict';
+
+const childProcess = require( 'child_process' );
+const crypto = require( 'crypto' );
+const path = require( 'path' );
+
+const ROOT_DIRECTORY = path.join( __dirname, '..', '..' );
+const { TRAVIS_JOB_TYPE } = process.env;
+
+const RED = '\x1B[0;31m';
+const GREEN = '\x1B[32m';
+const CYAN = '\x1B[36;1m';
+const MAGENTA = '\x1B[35;1m';
+const NO_COLOR = '\x1B[0m';
+
+// Tests + Code coverage.
+if ( TRAVIS_JOB_TYPE === 'Tests' ) {
+ console.log( `\n${ CYAN }Running the "Tests" build.${ NO_COLOR }\n` );
+
+ exec( 'node', './scripts/ci/check-packages-code-coverage.js' );
+}
+
+// Verifying the code style.
+if ( TRAVIS_JOB_TYPE === 'Validation' ) {
+ console.log( `\n${ CYAN }Running the "Validation" build.${ NO_COLOR }\n` );
+
+ // Linters.
+ exec( 'yarn', 'run', 'lint' );
+ exec( 'yarn', 'run', 'stylelint' );
+
+ // Verifying manual tests.
+ exec( 'yarn', 'run', 'dll:build' );
+ exec( 'sh', './scripts/check-manual-tests.sh', '-r', 'ckeditor5', '-f', 'ckeditor5' );
+
+ // Build the docs.
+ exec( 'yarn', 'run', 'docs', '--strict' );
+}
+
+/**
+ * Executes the specified command. E.g. for displaying the Node's version, use:
+ *
+ * exec( 'node', '-v' );
+ *
+ * The output will be formatted using Travis's structure that increases readability.
+ *
+ * @param {..String} command
+ */
+function exec( ...command ) {
+ const travis = {
+ _lastTimerId: null,
+ _lastStartTime: null,
+
+ foldStart() {
+ console.log( `travis_fold:start:script${ MAGENTA }$ ${ command.join( ' ' ) }${ NO_COLOR }` );
+ this._timeStart();
+ },
+
+ foldEnd() {
+ this._timeFinish();
+ console.log( '\ntravis_fold:end:script\n' );
+ },
+
+ _timeStart() {
+ const nanoSeconds = process.hrtime.bigint();
+
+ this._lastTimerId = crypto.createHash( 'md5' ).update( nanoSeconds.toString() ).digest( 'hex' );
+ this._lastStartTime = nanoSeconds;
+
+ // Intentional direct write to stdout, to manually control EOL.
+ process.stdout.write( `travis_time:start:${ this._lastTimerId }\r\n` );
+ },
+
+ _timeFinish() {
+ const travisEndTime = process.hrtime.bigint();
+ const duration = travisEndTime - this._lastStartTime;
+
+ // Intentional direct write to stdout, to manually control EOL.
+ process.stdout.write(
+ `\ntravis_time:end:${ this._lastTimerId }:start=${ this._lastStartTime },` +
+ `finish=${ travisEndTime },duration=${ duration }\r\n`
+ );
+ }
+ };
+
+ travis.foldStart();
+
+ const childProcessStatus = childProcess.spawnSync( command[ 0 ], command.slice( 1 ), {
+ encoding: 'utf8',
+ shell: true,
+ cwd: ROOT_DIRECTORY,
+ stdio: 'inherit',
+ stderr: 'inherit'
+ } );
+
+ const EXIT_CODE = childProcessStatus.status;
+ const COLOR = EXIT_CODE ? RED : GREEN;
+
+ travis.foldEnd();
+
+ console.log( `${ COLOR }The command "${ command.join( ' ' ) }" exited with ${ EXIT_CODE }.${ NO_COLOR }\n` );
+
+ if ( childProcessStatus.status ) {
+ // An error occurred. Break the entire script.
+ process.exit( EXIT_CODE );
+ }
+}
diff --git a/scripts/docs/build-and-publish-nightly.js b/scripts/docs/build-and-publish-nightly.js
index 93ba7fb27f1..3e1f63f91c9 100644
--- a/scripts/docs/build-and-publish-nightly.js
+++ b/scripts/docs/build-and-publish-nightly.js
@@ -15,13 +15,18 @@ This script is to be used on CI to automatically update https://ckeditor5.github
*/
+const buildBranch = process.env.TRAVIS_BRANCH;
+
// Build the documentation only when master branch is updated.
-if ( process.env.TRAVIS_BRANCH !== 'master' ) {
+if ( buildBranch !== 'master' ) {
+ printDebugLog( `Aborting due to an invalid branch (${ buildBranch }).` );
process.exit();
}
// Build the documentation only when a cron task triggered the CI.
if ( process.env.TRAVIS_EVENT_TYPE !== 'cron' ) {
+ printDebugLog( `Aborting due to an invalid event type (${ process.env.TRAVIS_EVENT_TYPE }).` );
+
process.exit();
}
@@ -110,3 +115,9 @@ function exec( command ) {
process.exit( 1 );
}
}
+
+function printDebugLog( message ) {
+ if ( process.env.DEBUG == 'true' ) {
+ console.log( '[Nightly Docs]', message );
+ }
+}
diff --git a/scripts/web-crawler/constants.js b/scripts/web-crawler/constants.js
index fcf10f7450f..d42ac292272 100644
--- a/scripts/web-crawler/constants.js
+++ b/scripts/web-crawler/constants.js
@@ -11,6 +11,8 @@ const DEFAULT_CONCURRENCY = require( 'os' ).cpus().length / 2;
const DEFAULT_TIMEOUT = 15 * 1000;
+const DEFAULT_RESPONSIVENESS_CHECK_TIMEOUT = 1000;
+
const DEFAULT_REMAINING_ATTEMPTS = 3;
const ERROR_TYPES = {
@@ -59,6 +61,7 @@ const DATA_ATTRIBUTE_NAME = 'data-cke-crawler-skip';
module.exports = {
DEFAULT_CONCURRENCY,
DEFAULT_TIMEOUT,
+ DEFAULT_RESPONSIVENESS_CHECK_TIMEOUT,
DEFAULT_REMAINING_ATTEMPTS,
ERROR_TYPES,
PATTERN_TYPE_TO_ERROR_TYPE_MAP,
diff --git a/scripts/web-crawler/index.js b/scripts/web-crawler/index.js
index ad5fc26e5e6..8556df2e7a2 100644
--- a/scripts/web-crawler/index.js
+++ b/scripts/web-crawler/index.js
@@ -19,6 +19,7 @@ const { createSpinner, getProgressHandler } = require( './spinner' );
const {
DEFAULT_TIMEOUT,
+ DEFAULT_RESPONSIVENESS_CHECK_TIMEOUT,
DEFAULT_REMAINING_ATTEMPTS,
ERROR_TYPES,
PATTERN_TYPE_TO_ERROR_TYPE_MAP,
@@ -242,12 +243,20 @@ async function openLink( browser, { baseUrl, link, foundLinks, exclusions } ) {
} );
}
- await page.close();
+ const isResponding = await isPageResponding( page );
- return {
- errors,
- links: []
- };
+ // Exit immediately and do not try to call any function in the context of the page, that is not responding or if it has not been
+ // opened. However, once the page has been opened (its URL is the same as the one requested), continue as usual and do not close
+ // the page yet, because the page may contain error exclusions, that should be taken into account. Such a case can happen when,
+ // for example, the `load` event was not fired because the external resource was not loaded yet.
+ if ( !isResponding || page.url() !== link.url ) {
+ await page.close();
+
+ return {
+ errors,
+ links: []
+ };
+ }
}
// Create patterns from meta tags to ignore errors.
@@ -539,6 +548,19 @@ function isNavigationRequest( request ) {
return request.isNavigationRequest() && request.frame().parentFrame() === null;
}
+/**
+ * Checks, if the page is not hung by trying to evaluate a function within the page context in defined time.
+ *
+ * @param {Object} page The page instance from Puppeteer.
+ * @returns {Promise.}
+ */
+async function isPageResponding( page ) {
+ return Promise.race( [
+ page.title(),
+ new Promise( ( resolve, reject ) => setTimeout( () => reject(), DEFAULT_RESPONSIVENESS_CHECK_TIMEOUT ) )
+ ] ).then( () => true ).catch( () => false );
+}
+
/**
* Registers a request interception procedure to explicitly block all 'media' requests (resources loaded by a