Skip to content

Commit

Permalink
FEATURE: Implement non-breaking space
Browse files Browse the repository at this point in the history
  • Loading branch information
Sebobo committed Jun 21, 2021
1 parent 86d4320 commit 40bcf58
Show file tree
Hide file tree
Showing 12 changed files with 174 additions and 49 deletions.
46 changes: 29 additions & 17 deletions README.md
Expand Up @@ -6,9 +6,9 @@

## Introduction

This package provides a button to insert soft hyphens for the inline editor in Neos CMS.
This package provides a button to insert soft hyphens and non-reaking spaces for the inline editor in Neos CMS.

Many browser support some kind of hyphenation via CSS but it doesnt work reliably on all systems
Many browser support some kind of hyphenation via CSS, but it doesn't work reliably on all systems
and not with all languages. See the [CanIuse](https://caniuse.com/#feat=css-hyphens) table for details.

Therefore this package provides a manual way to insert them. You should be careful with using the
Expand Down Expand Up @@ -37,8 +37,8 @@ And in action:
## Advantages

* Your editors gain control over word breaks.
* Stores the hyphens in the database with their UTF8 representation which the browser interprets as `­`.
* You don't need other characters which you replace with hyphens in the Frontend.
* Stores the special character in the database with their UTF8 representation which the browser interprets as `­`.
* You don't need other characters which you replace with correct characters in the Frontend.
* Should work fine with Elasticsearch and other search engines.

### Planned features
Expand All @@ -49,31 +49,43 @@ See [enhancement issue list](https://github.com/Sebobo/Shel.Neos.Hyphens/issues?

Run this in your site package

```console
composer require --no-update shel/neos-hyphens
```
composer require --no-update shel/neos-hyphens

Then run `composer update` in your project directory.

## How to use

### Hyphen

Enable it for a node with editable text like this:

```yaml
"Neos.NodeTypes:Text":
properties:
text:
ui:
inline:
editorOptions:
hyphens: true
```
'Neos.NodeTypes:Text':
properties:
text:
ui:
inline:
editorOptions:
hyphens: true

This will add a new button to insert a soft hyphen. As an alternative, you can use the shortcut `Ctrl + Shift + -` to add a new soft hyphen. To change the keys of the shortcut, have a look at the [Settings.yaml](Configuration/Settings.yaml#L12) file.

### Non-breaking space

Enable it for a node with editable text like this:

'Neos.NodeTypes:Text':
properties:
text:
ui:
inline:
editorOptions:
nbsp: true

This will add a new button to insert a non-breaking space.

## Customization

### Hyphen styling in the backend
### Hyphen & non-breaking space styling in the backend

You can provide your own styling by referencing your own stylesheet.
See the file `Override.Page.fusion` on how the default styling is included.
Expand Down
11 changes: 11 additions & 0 deletions Resources/Private/Scripts/HyphensEditor/src/commands/nbsp.js
@@ -0,0 +1,11 @@
import {Command} from 'ckeditor5-exports';

export default class NbspCommand extends Command {
execute() {
const editor = this.editor;
editor.model.change((writer) => {
const insertPosition = editor.model.document.selection.getFirstPosition();
writer.insertText('\u00A0', insertPosition);
});
}
}
Expand Up @@ -13,7 +13,7 @@ const addPlugin = (Plugin, isEnabled) => (ckEditorConfiguration, options) => {
export default (ckEditorRegistry, editorConfig) => {
const config = ckEditorRegistry.get('config');

config.set('hyphens', addPlugin(Hyphens(editorConfig), $get('hyphens')));
config.set('hyphens', addPlugin(Hyphens(editorConfig), $get('hyphens') || $get('nbsp')));

return config;
};
Expand Up @@ -3,16 +3,16 @@ import PropTypes from 'prop-types';
import {$get} from 'plow-js';
import {Button} from '@neos-project/react-ui-components';
import {neos} from '@neos-project/neos-ui-decorators';
import hyphensButtonTheme from './hyphensButtonTheme.css';
import buttonTheme from './hyphensButtonTheme.css';
import {themr} from '@friendsofreactjs/react-css-themr';

const BUTTON_PROPS = ['formattingRule', 'inlineEditorOptions', 'i18nRegistry', 'tooltip', 'isActive', 'label'];

@neos(globalRegistry => ({
i18nRegistry: globalRegistry.get('i18n')
}))
@themr('HyphensButton', hyphensButtonTheme)
class ButtonComponent extends PureComponent {
@themr('HyphensButton', buttonTheme)
class HyphenButtonComponent extends PureComponent {
static propTypes = {
i18nRegistry: PropTypes.object,
tooltip: PropTypes.string
Expand All @@ -24,12 +24,11 @@ class ButtonComponent extends PureComponent {
return carry;
}, {});
return (
<Button {...finalProps} isActive={Boolean(this.props.isActive)} className={hyphensButtonTheme['btn--no-padding']}
<Button {...finalProps} isActive={Boolean(this.props.isActive)} className={buttonTheme['btn--no-padding']}
title={this.props.i18nRegistry.translate(this.props.tooltip)}>
<svg xmlns="http://www.w3.org/2000/svg" width="18.109" height="9.697" viewBox="0, 0, 18.109, 9.697">
<g stroke="currentColor" stroke-miterlimit="3.864" fill="none">
<path d="M2.596 1a5.44 5.44 0 0 0 0 7.697m12.918 0a5.44 5.44 0 0 0 0-7.697"
stroke-width=".907"/>
<path d="M2.596 1a5.44 5.44 0 0 0 0 7.697m12.918 0a5.44 5.44 0 0 0 0-7.697" stroke-width=".907"/>
<path d="M4.52 4.848h9.07" stroke-width="1.814"/>
</g>
</svg>
Expand All @@ -38,6 +37,35 @@ class ButtonComponent extends PureComponent {
}
}


@neos(globalRegistry => ({
i18nRegistry: globalRegistry.get('i18n')
}))
@themr('NbspButton', buttonTheme)
class NbspButtonComponent extends PureComponent {
static propTypes = {
i18nRegistry: PropTypes.object,
tooltip: PropTypes.string
};

render() {
const finalProps = Object.keys(this.props).reduce((carry, key) => {
if (BUTTON_PROPS.indexOf(key) === -1) carry[key] = this.props[key];
return carry;
}, {});
return (
<Button {...finalProps} isActive={Boolean(this.props.isActive)} className={buttonTheme['btn--no-padding']}
title={this.props.i18nRegistry.translate(this.props.tooltip)}>
<svg width="18px" height="9px" viewBox="0 0 18 9" version="1.1" xmlns="http://www.w3.org/2000/svg">
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="square">
<path d="M16.3846154,2.61538462 L16.3846154,7.53846154 M16.3846154,7.53846154 L1.61538462,7.53846154 M1.61538462,2.61538462 L1.61538462,7.53846154" id="Combined-Shape" stroke="#FFFFFF" stroke-width="1.8"></path>
</g>
</svg>
</Button>
);
}
}

//
// Modify richtext editing toolbar registry
//
Expand All @@ -47,7 +75,7 @@ export default ckEditorRegistry => {
richtextToolbar.set('shy', {
label: 'Shy',
commandName: 'insertShyEntity',
component: ButtonComponent,
component: HyphenButtonComponent,
callbackPropName: 'onClick',
style: 'transparent',
hoverStyle: 'brand',
Expand All @@ -56,5 +84,17 @@ export default ckEditorRegistry => {
isActive: $get('shy')
});

richtextToolbar.set('nbsp', {
label: 'Nbsp',
commandName: 'insertNbspEntity',
component: NbspButtonComponent,
callbackPropName: 'onClick',
style: 'transparent',
hoverStyle: 'brand',
tooltip: 'Shel.Neos.Hyphens:Main:ckeditor__toolbar__nbsp',
isVisible: $get('nbsp'),
isActive: $get('nbsp'),
});

return richtextToolbar;
};
41 changes: 20 additions & 21 deletions Resources/Private/Scripts/HyphensEditor/src/plugins/hyphens.js
@@ -1,8 +1,10 @@
import {Plugin, ViewRange} from 'ckeditor5-exports';
import ShyCommand from '../commands/shy';
import NbspCommand from '../commands/nbsp';
import './hyphens.vanilla-css';

const softHyphenCharacter = '\u00AD';
const nbspCharacter = '\u00A0';

function HyphensFactory(config) {
return class Hyphens extends Plugin {
Expand All @@ -14,6 +16,8 @@ function HyphensFactory(config) {
const {editor} = this;

editor.commands.add('insertShyEntity', new ShyCommand(this.editor));
editor.commands.add('insertNbspEntity', new NbspCommand(this.editor));

if (config.shortcut) {
editor.keystrokes.set(config.shortcut, 'insertShyEntity');
}
Expand All @@ -30,37 +34,32 @@ function HyphensFactory(config) {
let modelPosition = data.range.start;
let viewPosition = conversionApi.mapper.toViewPosition(modelPosition);

const dataChunks = data.item.data.split(softHyphenCharacter);
const dataChunks = data.item.data.split(/([\u00AD,\u00A0])/);

for (let i = 0; i < dataChunks.length; i++) {
const chunk = dataChunks[i];

if (chunk !== '') {
viewWriter.insert(viewPosition, viewWriter.createText(chunk));
if (chunk === '') continue;

// Need to recalculate `viewPosition` after every inserted item.
modelPosition = modelPosition.getShiftedBy(chunk.length);
viewPosition = conversionApi.mapper.toViewPosition(modelPosition);
}
viewWriter.insert(viewPosition, viewWriter.createText(chunk));

// Do not insert anything after the last chunk
if (i === dataChunks.length - 1) {
break;
}
// Wrap special characters with spans and matching classes for styling
if (chunk === nbspCharacter || chunk === softHyphenCharacter) {
const characterClass = chunk === nbspCharacter ? 'nbsp' : 'shy';
const viewSpaceSpan = viewWriter.createAttributeElement('span', {
class: characterClass,
});
const modelWrapRange = new ViewRange(modelPosition, modelPosition.getShiftedBy(1));
const viewWrapRange = conversionApi.mapper.toViewRange(modelWrapRange);

// Insert utf8 dash character as representation
// We will wrap in in a span in following lines.
viewWriter.insert(viewPosition, viewWriter.createText(softHyphenCharacter));

const viewSpaceSpan = viewWriter.createAttributeElement('span', {class: 'shy'});
const modelWrapRange = new ViewRange(modelPosition, modelPosition.getShiftedBy(1));
const viewWrapRange = conversionApi.mapper.toViewRange(modelWrapRange);

viewWriter.wrap(viewWrapRange, viewSpaceSpan);
viewWriter.wrap(viewWrapRange, viewSpaceSpan);
}

// Need to recalculate `viewPosition` after every inserted item.
modelPosition = modelPosition.getShiftedBy(1);
modelPosition = modelPosition.getShiftedBy(chunk.length);
viewPosition = conversionApi.mapper.toViewPosition(modelPosition);
}
evt.stop();
}, {priority: 'high'});
});
}
Expand Down
Expand Up @@ -13,3 +13,31 @@
right: .1em;
top: 50%;
}

.ck-content .nbsp {
padding: 0 0.4em;
position: relative;
border-radius: 3px;
margin: 0 0.3em;
position: relative;
}

.ck-content .nbsp::before {
content: '';
border-bottom: 2px solid currentColor;
width: 100%;
position: absolute;
right: 0;
bottom: 3px;
}

.ck-content .nbsp::after {
content: '';
width: 100%;
height: 5px;
position: absolute;
bottom: 4px;
right: 0;
border-left: 2px solid currentColor;
border-right: 2px solid currentColor;
}
4 changes: 4 additions & 0 deletions Resources/Private/Translations/de/Main.xlf
Expand Up @@ -6,6 +6,10 @@
<source>Soft hyphen</source>
<target>Weiches Trennzeichen</target>
</trans-unit>
<trans-unit id="ckeditor__toolbar__nbsp" xml:space="preserve">
<source>Non-breaking space</source>
<target>Geschütztes Trennzeichen</target>
</trans-unit>
</body>
</file>
</xliff>
3 changes: 3 additions & 0 deletions Resources/Private/Translations/en/Main.xlf
Expand Up @@ -5,6 +5,9 @@
<trans-unit id="ckeditor__toolbar__shy" xml:space="preserve">
<source>Soft hyphen</source>
</trans-unit>
<trans-unit id="ckeditor__toolbar__nbsp" xml:space="preserve">
<source>Non-breaking space</source>
</trans-unit>
</body>
</file>
</xliff>
28 changes: 28 additions & 0 deletions Resources/Public/HyphensEditor/Plugin.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Resources/Public/HyphensEditor/Plugin.css.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Resources/Public/HyphensEditor/Plugin.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Resources/Public/HyphensEditor/Plugin.js.map

Large diffs are not rendered by default.

0 comments on commit 40bcf58

Please sign in to comment.