diff --git a/docs/features/font.md b/docs/features/font.md
index e84cace..e63fdba 100644
--- a/docs/features/font.md
+++ b/docs/features/font.md
@@ -21,7 +21,7 @@ The {@link module:font/font~Font} plugin enables the following features in the r
## Configuring the font family feature
-It is possible to configure which font family options are supported by the WYSIWYG editor. Use the {@link module:font/fontfamily~FontFamilyConfig#options `fontFamily.options`} configuration option to do so.
+It is possible to configure which font family options are supported by the WYSIWYG editor. Use the {@link module:font/fontfamily~FontFamilyConfig#options `config.fontFamily.options`} configuration option to do so.
Use the special `'default'` keyword to use the default font family defined in the web page styles. It removes any custom font family.
@@ -47,9 +47,28 @@ ClassicEditor
{@snippet features/custom-font-family-options}
+### Accept all font names
+
+By default, all `font-family` values that are not specified in the `config.fontFamily.options` are stripped. You can enable support for all font names by using the {@link module:font/fontfamily~FontFamilyConfig#supportAllValues `config.fontFamily.supportAllValues`} option.
+
+```js
+ClassicEditor
+ .create( document.querySelector( '#editor' ), {
+ fontFamily: {
+ options: [
+ // ...
+ ],
+ supportAllValues: true
+ },
+ // ...
+ } )
+ .then( ... )
+ .catch( ... );
+```
+
## Configuring the font size feature
-It is possible to configure which font size options are supported by the WYSIWYG editor. Use the {@link module:font/fontsize~FontSizeConfig#options `fontSize.options`} configuration option to do so.
+It is possible to configure which font size options are supported by the WYSIWYG editor. Use the {@link module:font/fontsize~FontSizeConfig#options `config.fontSize.options`} configuration option to do so.
Use the special `'default'` keyword to use the default font size defined in the web page styles. It removes any custom font size.
@@ -150,6 +169,26 @@ ClassicEditor
{@snippet features/custom-font-size-numeric-options}
+### Prevent removing non-specified values
+
+By default, all `font-size` values that are not specified in the `config.fontSize.options` are stripped. You can enable support for all font sizes by using the {@link module:font/fontfamily~FontSizeConfig#supportAllValues `config.fontSize.supportAllValues`} option.
+
+
+```js
+ClassicEditor
+ .create( document.querySelector( '#editor' ), {
+ fontSize: {
+ options: [
+ // ...
+ ],
+ supportAllValues: true
+ },
+ // ...
+ } )
+ .then( ... )
+ .catch( ... );
+```
+
## Configuring the font color and font background color features
Both font color and font background color features are configurable and share the same configuration format.
@@ -164,7 +203,7 @@ Check out the WYSIWYG editor below with both features customized using the edito
### Specifying available colors
-It is possible to configure which colors are available in the color dropdown. Use the {@link module:font/fontcolor~FontColorConfig#colors `fontColor.colors`} and {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#colors `fontBackgroundColor.colors`} configuration options to do so.
+It is possible to configure which colors are available in the color dropdown. Use the {@link module:font/fontcolor~FontColorConfig#colors `config.fontColor.colors`} and {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#colors `config.fontBackgroundColor.colors`} configuration options to do so.
```js
ClassicEditor
@@ -232,7 +271,7 @@ ClassicEditor
### Changing the geometry of the color grid
-You can configure the number of columns in the color dropdown by setting the {@link module:font/fontcolor~FontColorConfig#columns `fontColor.columns`} and {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#columns `fontBackgroundColor.columns`} configuration options.
+You can configure the number of columns in the color dropdown by setting the {@link module:font/fontcolor~FontColorConfig#columns `config.fontColor.columns`} and {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#columns `config.fontBackgroundColor.columns`} configuration options.
Usually, you will want to use this option when changing the number of [available colors](#specifying-available-colors).
@@ -265,7 +304,7 @@ ClassicEditor
The font and font background color dropdowns contain the "Document colors" section. It lists the colors already used in the document for the users to be able to easily reuse them (for consistency purposes).
-By default, the number of displayed document colors is limited to one row, but you can adjust it (or remove the whole section) by using the {@link module:font/fontcolor~FontColorConfig#documentColors `fontColor.documentColors`} or {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#documentColors `fontBackgroundColor.documentColors`} options.
+By default, the number of displayed document colors is limited to one row, but you can adjust it (or remove the whole section) by using the {@link module:font/fontcolor~FontColorConfig#documentColors `config.fontColor.documentColors`} or {@link module:font/fontbackgroundcolor~FontBackgroundColorConfig#documentColors `config.fontBackgroundColor.documentColors`} options.
```js
ClassicEditor
@@ -340,7 +379,7 @@ The {@link module:font/fontfamily~FontFamily} plugin registers the following com
* The `'fontFamily'` dropdown.
* The {@link module:font/fontfamily/fontfamilycommand~FontFamilyCommand `'fontFamily'`} command.
- The number of options and their names correspond to the {@link module:font/fontfamily~FontFamilyConfig#options `fontFamily.options`} configuration option.
+ The number of options and their names correspond to the {@link module:font/fontfamily~FontFamilyConfig#options `config.fontFamily.options`} configuration option.
You can change the font family of the current selection by executing the command with a desired value:
@@ -386,7 +425,7 @@ The {@link module:font/fontsize~FontSize} plugin registers the following compone
* The `'fontSize'` dropdown.
* The {@link module:font/fontsize/fontsizecommand~FontSizeCommand `'fontSize'`} command.
- The number of options and their names correspond to the {@link module:font/fontsize~FontSizeConfig#options `fontSize.options`} configuration option.
+ The number of options and their names correspond to the {@link module:font/fontsize~FontSizeConfig#options `config.fontSize.options`} configuration option.
You can change the font size of the current selection by executing the command with a desired value:
@@ -398,7 +437,7 @@ The {@link module:font/fontsize~FontSize} plugin registers the following compone
editor.execute( 'fontSize', { value: 'small' } );
```
- Passing an empty value will remove any `fontSize` set:
+ Passing an empty value will remove any `config.fontSize` set:
```js
editor.execute( 'fontSize' );
diff --git a/src/fontfamily.js b/src/fontfamily.js
index cbea666..dc7912d 100644
--- a/src/fontfamily.js
+++ b/src/fontfamily.js
@@ -112,3 +112,19 @@ export default class FontFamily extends Plugin {
*
* @member {Array.} module:font/fontfamily~FontFamilyConfig#options
*/
+
+/**
+ * By default the plugin removes any `font-family` value that does not match to the plugin's configuration. It means if you paste a content
+ * with font families that the editor does not understand, the font-family attribute will be removed and the content will be displayed
+ * with the font.
+ *
+ * You can preserve pasted font family values by switching the option:
+ *
+ * const fontSizeConfig = {
+ * supportAllValues: true
+ * };
+ *
+ * Now, the font families, not specified in the editor's configuration, won't be removed when pasting the content.
+ *
+ * @member {Boolean} module:font/fontfamily~FontFamilyConfig#supportAllValues
+ */
diff --git a/src/fontfamily/fontfamilyediting.js b/src/fontfamily/fontfamilyediting.js
index 39e522f..3faea51 100644
--- a/src/fontfamily/fontfamilyediting.js
+++ b/src/fontfamily/fontfamilyediting.js
@@ -49,7 +49,8 @@ export default class FontFamilyEditing extends Plugin {
'Times New Roman, Times, serif',
'Trebuchet MS, Helvetica, sans-serif',
'Verdana, Geneva, sans-serif'
- ]
+ ],
+ supportAllValues: false
} );
}
@@ -71,8 +72,42 @@ export default class FontFamilyEditing extends Plugin {
const definition = buildDefinition( FONT_FAMILY, options );
// Set-up the two-way conversion.
- editor.conversion.attributeToElement( definition );
+ if ( editor.config.get( 'fontFamily.supportAllValues' ) ) {
+ this._prepareAnyValueConverters();
+ } else {
+ editor.conversion.attributeToElement( definition );
+ }
editor.commands.add( FONT_FAMILY, new FontFamilyCommand( editor ) );
}
+
+ /**
+ * Those converters enable keeping any value found as `style="font-family: *"` as a value of an attribute on a text even
+ * if it isn't defined in the plugin configuration.
+ *
+ * @private
+ */
+ _prepareAnyValueConverters() {
+ const editor = this.editor;
+
+ editor.conversion.for( 'downcast' ).attributeToElement( {
+ model: FONT_FAMILY,
+ view: ( attributeValue, writer ) => {
+ return writer.createAttributeElement( 'span', { style: 'font-family:' + attributeValue }, { priority: 7 } );
+ }
+ } );
+
+ editor.conversion.for( 'upcast' ).attributeToAttribute( {
+ model: {
+ key: FONT_FAMILY,
+ value: viewElement => viewElement.getStyle( 'font-family' )
+ },
+ view: {
+ name: 'span',
+ styles: {
+ 'font-family': /.*/
+ }
+ }
+ } );
+ }
}
diff --git a/src/fontfamily/fontfamilyui.js b/src/fontfamily/fontfamilyui.js
index 49976b1..0c4d035 100644
--- a/src/fontfamily/fontfamilyui.js
+++ b/src/fontfamily/fontfamilyui.js
@@ -109,7 +109,18 @@ function _prepareListOptions( options, command ) {
} )
};
- def.model.bind( 'isOn' ).to( command, 'value', value => value === option.model );
+ def.model.bind( 'isOn' ).to( command, 'value', value => {
+ // "Default" or check in strict font-family converters mode.
+ if ( value === option.model ) {
+ return true;
+ }
+
+ if ( !value || !option.model ) {
+ return false;
+ }
+
+ return value.split( ',' )[ 0 ].replace( /'/g, '' ).toLowerCase() === option.model.toLowerCase();
+ } );
// Try to set a dropdown list item style.
if ( option.view && option.view.styles ) {
diff --git a/src/fontsize.js b/src/fontsize.js
index 5a8c86a..5047d11 100644
--- a/src/fontsize.js
+++ b/src/fontsize.js
@@ -105,6 +105,22 @@ export default class FontSize extends Plugin {
* options: [ 9, 10, 11, 12, 13, 14, 15 ]
* };
*
+ * Also, you can define a label in the dropdown for numerical values:
+ *
+ * const fontSizeConfig = {
+ * options: [
+ * {
+ * title: 'Small',
+ * model: '8px
+ * },
+ * 'default',
+ * {
+ * title: 'Big',
+ * model: '14px
+ * }
+ * ]
+ * };
+ *
* Font size can be applied using the command API. To do that, use the `'fontSize'` command and pass the desired font size as a `value`.
* For example, the following code will apply the `fontSize` attribute with the **tiny** value to the current selection:
*
@@ -114,3 +130,19 @@ export default class FontSize extends Plugin {
*
* @member {Array.} module:font/fontsize~FontSizeConfig#options
*/
+
+/**
+ * By default the plugin removes any `font-size` value that does not match to the plugin's configuration. It means if you paste a content
+ * with font sizes that the editor does not understand, the font-size attribute will be removed and the content will be displayed
+ * with the default size.
+ *
+ * You can preserve pasted font size values by switching the option:
+ *
+ * const fontSizeConfig = {
+ * supportAllValues: true
+ * };
+ *
+ * Now, the font sizes, not specified in the editor's configuration, won't be removed when pasting the content.
+ *
+ * @member {Boolean} module:font/fontsize~FontSizeConfig#supportAllValues
+ */
diff --git a/src/fontsize/fontsizeediting.js b/src/fontsize/fontsizeediting.js
index 6780199..b44b10b 100644
--- a/src/fontsize/fontsizeediting.js
+++ b/src/fontsize/fontsizeediting.js
@@ -48,31 +48,70 @@ export default class FontSizeEditing extends Plugin {
'default',
'big',
'huge'
- ]
+ ],
+ supportAllValues: false
} );
+ }
+
+ /**
+ * @inheritDoc
+ */
+ init() {
+ const editor = this.editor;
+
+ // Allow fontSize attribute on text nodes.
+ editor.model.schema.extend( '$text', { allowAttributes: FONT_SIZE } );
+ editor.model.schema.setAttributeProperties( FONT_SIZE, {
+ isFormatting: true,
+ copyOnEnter: true
+ } );
+
+ const supportAllValues = editor.config.get( 'fontSize.supportAllValues' );
// Define view to model conversion.
- const options = normalizeOptions( this.editor.config.get( 'fontSize.options' ) ).filter( item => item.model );
+ const options = normalizeOptions( this.editor.config.get( 'fontSize.options' ), { supportAllValues } )
+ .filter( item => item.model );
const definition = buildDefinition( FONT_SIZE, options );
// Set-up the two-way conversion.
- editor.conversion.attributeToElement( definition );
+ if ( supportAllValues ) {
+ this._prepareAnyValueConverters();
+ } else {
+ editor.conversion.attributeToElement( definition );
+ }
// Add FontSize command.
editor.commands.add( FONT_SIZE, new FontSizeCommand( editor ) );
}
/**
- * @inheritDoc
+ * Those converters enable keeping any value found as `style="font-size: *"` as a value of an attribute on a text even
+ * if it isn't defined in the plugin configuration.
+ *
+ * @private
*/
- init() {
+ _prepareAnyValueConverters() {
const editor = this.editor;
- // Allow fontSize attribute on text nodes.
- editor.model.schema.extend( '$text', { allowAttributes: FONT_SIZE } );
- editor.model.schema.setAttributeProperties( FONT_SIZE, {
- isFormatting: true,
- copyOnEnter: true
+ editor.conversion.for( 'downcast' ).attributeToElement( {
+ model: FONT_SIZE,
+ view: ( attributeValue, writer ) => {
+ if ( !attributeValue ) {
+ return;
+ }
+
+ return writer.createAttributeElement( 'span', { style: 'font-size:' + attributeValue }, { priority: 7 } );
+ }
+ } );
+
+ editor.conversion.for( 'upcast' ).attributeToAttribute( {
+ model: {
+ key: FONT_SIZE,
+ value: viewElement => viewElement.getStyle( 'font-size' )
+ },
+ view: {
+ name: 'span'
+ }
} );
}
}
diff --git a/src/fontsize/utils.js b/src/fontsize/utils.js
index d41cb21..86635d8 100644
--- a/src/fontsize/utils.js
+++ b/src/fontsize/utils.js
@@ -7,75 +7,105 @@
* @module font/fontsize/utils
*/
+import CKEditorError from '@ckeditor/ckeditor5-utils/src/ckeditorerror';
+
/**
* Normalizes and translates the {@link module:font/fontsize~FontSizeConfig#options configuration options}
* to the {@link module:font/fontsize~FontSizeOption} format.
*
* @param {Array.} configuredOptions An array of options taken from the configuration.
+ * @param {Object} [options={}]
+ * @param {Boolean} [options.supportAllValues=false]
* @returns {Array.}
*/
-export function normalizeOptions( configuredOptions ) {
+export function normalizeOptions( configuredOptions, options = {} ) {
+ const supportAllValues = options.supportAllValues || false;
+
// Convert options to objects.
return configuredOptions
- .map( getOptionDefinition )
+ .map( item => getOptionDefinition( item, supportAllValues ) )
// Filter out undefined values that `getOptionDefinition` might return.
.filter( option => !!option );
}
-// Default named presets map.
+// The values should be synchronized with values specified in the "/theme/fontsize.css" file.
+export const FONT_SIZE_PRESET_UNITS = {
+ tiny: '0.7em',
+ small: '0.85em',
+ big: '1.4em',
+ huge: '1.8em'
+};
+
+// Default named presets map. Always create a new instance of the preset object in order to avoid modifying references.
const namedPresets = {
- tiny: {
- title: 'Tiny',
- model: 'tiny',
- view: {
- name: 'span',
- classes: 'text-tiny',
- priority: 7
- }
+ get tiny() {
+ return {
+ title: 'Tiny',
+ model: 'tiny',
+ view: {
+ name: 'span',
+ classes: 'text-tiny',
+ priority: 7
+ }
+ };
},
- small: {
- title: 'Small',
- model: 'small',
- view: {
- name: 'span',
- classes: 'text-small',
- priority: 7
- }
+ get small() {
+ return {
+ title: 'Small',
+ model: 'small',
+ view: {
+ name: 'span',
+ classes: 'text-small',
+ priority: 7
+ }
+ };
},
- big: {
- title: 'Big',
- model: 'big',
- view: {
- name: 'span',
- classes: 'text-big',
- priority: 7
- }
+ get big() {
+ return {
+ title: 'Big',
+ model: 'big',
+ view: {
+ name: 'span',
+ classes: 'text-big',
+ priority: 7
+ }
+ };
},
- huge: {
- title: 'Huge',
- model: 'huge',
- view: {
- name: 'span',
- classes: 'text-huge',
- priority: 7
- }
+ get huge() {
+ return {
+ title: 'Huge',
+ model: 'huge',
+ view: {
+ name: 'span',
+ classes: 'text-huge',
+ priority: 7
+ }
+ };
}
};
// Returns an option definition either from preset or creates one from number shortcut.
// If object is passed then this method will return it without alternating it. Returns undefined for item than cannot be parsed.
+// If supportAllValues=true, model will be set to a specified unit value instead of text.
//
// @param {String|Number|Object} item
+// @param {Boolean} supportAllValues
// @returns {undefined|module:font/fontsize~FontSizeOption}
-function getOptionDefinition( option ) {
- // Treat any object as full item definition provided by user in configuration.
- if ( typeof option === 'object' ) {
- return option;
+function getOptionDefinition( option, supportAllValues ) {
+ // Check whether passed option is a full item definition provided by user in configuration.
+ if ( isFullItemDefinition( option ) ) {
+ return attachPriority( option );
}
+ const preset = findPreset( option );
+
// Item is a named preset.
- if ( namedPresets[ option ] ) {
- return namedPresets[ option ];
+ if ( preset ) {
+ if ( supportAllValues ) {
+ preset.model = FONT_SIZE_PRESET_UNITS[ option ];
+ }
+
+ return attachPriority( preset );
}
// 'Default' font size. It will be used to remove the fontSize attribute.
@@ -87,33 +117,97 @@ function getOptionDefinition( option ) {
}
// At this stage we probably have numerical value to generate a preset so parse it's value.
- const sizePreset = parseFloat( option );
-
// Discard any faulty values.
- if ( isNaN( sizePreset ) ) {
+ if ( isNumericalDefinition( option ) ) {
return;
}
// Return font size definition from size value.
- return generatePixelPreset( sizePreset );
+ return generatePixelPreset( option );
}
// Creates a predefined preset for pixel size.
//
-// @param {Number} size Font size in pixels.
+// @param {Number} definition Font size in pixels.
// @returns {module:font/fontsize~FontSizeOption}
-function generatePixelPreset( size ) {
- const sizeName = String( size );
-
- return {
- title: sizeName,
- model: size,
- view: {
- name: 'span',
- styles: {
- 'font-size': `${ size }px`
- },
- priority: 7
+function generatePixelPreset( definition ) {
+ // Extend a short (numeric value) definition.
+ if ( typeof definition === 'number' || typeof definition === 'string' ) {
+ definition = {
+ title: String( definition ),
+ model: `${ parseFloat( definition ) }px`
+ };
+ }
+
+ definition.view = {
+ name: 'span',
+ styles: {
+ 'font-size': definition.model
}
};
+
+ return attachPriority( definition );
+}
+
+// Adds the priority to the view element definition if missing. It's required due to ckeditor/ckeditor5#2291
+//
+// @param {Object} definition
+// @param {Object} definition.title
+// @param {Object} definition.model
+// @param {Object} definition.view
+// @returns {Object}
+function attachPriority( definition ) {
+ if ( !definition.view.priority ) {
+ definition.view.priority = 7;
+ }
+
+ return definition;
+}
+
+// Returns a prepared preset definition. If passed an object, a name of preset should be defined as `model` value.
+//
+// @param {String|Object} definition
+// @param {String} definition.model A preset name.
+// @returns {Object|undefined}
+function findPreset( definition ) {
+ return namedPresets[ definition ] || namedPresets[ definition.model ];
+}
+
+// We treat `definition` as completed if it is an object that contains `title`, `model` and `view` values.
+//
+// @param {Object} definition
+// @param {String} definition.title
+// @param {String} definition.model
+// @param {Object} definition.view
+// @returns {Boolean}
+function isFullItemDefinition( definition ) {
+ return typeof definition === 'object' && definition.title && definition.model && definition.view;
+}
+
+// We treat `definition` as numerical if it is a number, number-like (string) or an object with the `title` key.
+//
+// @param {Object|Number|String} definition
+// @param {Object} definition.title
+// @returns {Boolean}
+function isNumericalDefinition( definition ) {
+ let numberValue;
+
+ if ( typeof definition === 'object' ) {
+ if ( !definition.model ) {
+ /**
+ * Provided value as an option for {@link module:font/fontsize~FontSize} seems to invalid.
+ *
+ * See valid examples described in the {@link module:font/fontsize~FontSizeConfig#options plugin configuration}.
+ *
+ * @error font-size-invalid-definition
+ */
+ throw new CKEditorError( 'font-size-invalid-definition: Provided font size definition is invalid.', null, definition );
+ } else {
+ numberValue = parseFloat( definition.model );
+ }
+ } else {
+ numberValue = parseFloat( definition );
+ }
+
+ return isNaN( numberValue );
}
diff --git a/tests/fontfamily/fontfamilyediting.js b/tests/fontfamily/fontfamilyediting.js
index 72fc73f..a973996 100644
--- a/tests/fontfamily/fontfamilyediting.js
+++ b/tests/fontfamily/fontfamilyediting.js
@@ -66,6 +66,70 @@ describe( 'FontFamilyEditing', () => {
'Trebuchet MS, Helvetica, sans-serif',
'Verdana, Geneva, sans-serif'
] );
+
+ expect( editor.config.get( 'fontFamily.supportAllValues' ) ).to.equal( false );
+ } );
+ } );
+
+ describe( 'supportAllValues=true', () => {
+ let editor, doc;
+
+ beforeEach( () => {
+ return VirtualTestEditor
+ .create( {
+ plugins: [ FontFamilyEditing, Paragraph ],
+ fontFamily: {
+ options: [
+ 'Arial'
+ ],
+ supportAllValues: true
+ }
+ } )
+ .then( newEditor => {
+ editor = newEditor;
+
+ doc = editor.model;
+ } );
+ } );
+
+ afterEach( () => {
+ return editor.destroy();
+ } );
+
+ describe( 'editing pipeline conversion', () => {
+ it( 'should convert unknown fontFamily attribute values', () => {
+ setModelData( doc, 'f<$text fontFamily="foo-bar">o$text>o' );
+
+ expect( editor.getData() ).to.equal( '
' );
+ } );
+ } );
+
+ describe( 'data pipeline conversions', () => {
+ it( 'should convert from an element with defined style when with other styles', () => {
+ const data = '
' );
+ } );
+ } );
+
+ describe( 'data pipeline conversions', () => {
+ it( 'should convert from an element with defined style when with other styles', () => {
+ const data = '