Skip to content

Commit 00a8b9f

Browse files
committed
setup plugin
0 parents  commit 00a8b9f

File tree

19 files changed

+10584
-0
lines changed

19 files changed

+10584
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

Configuration/Settings.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Neos:
2+
Neos:
3+
Ui:
4+
resources:
5+
javascript:
6+
'Prgfx.Neos.TextPartLanguage':
7+
resource: resource://Prgfx.Neos.TextPartLanguage/Public/JavaScript/TextPartLanguage/Plugin.js
8+
frontendConfiguration:
9+
'Prgfx.Neos.TextPartLanguage:languages': ${Configuration.setting('Neos.Neos.userInterface.availableLanguages')}
10+
userInterface:
11+
translation:
12+
autoInclude:
13+
'Prgfx.Neos.TextPartLanguage':
14+
- 'Editor'
15+
fusion:
16+
autoInclude:
17+
'Prgfx.Neos.TextPartLanguage': true

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Prgfx.Neos.TextPartLanguage
2+
3+
This package allows editors to tag selected text with a language, helping assistive technology with pronunciation.
4+
5+
```
6+
composer require prgfx/neos-textpartlanguage
7+
```
8+
9+
This plugin is based on [@ckeditor/ckeditor5-language](https://github.com/ckeditor/ckeditor5/tree/master/packages/ckeditor5-language), making the necessary adaptations to use it with the Neos backend.
10+
11+
![Screenshot of the plugin in the Neos backend](./Documentation/assets/preview.png)
12+
13+
## Usage
14+
Enable it for a node property:
15+
```yaml
16+
'Neos.Demo:Content.Text':
17+
properties:
18+
text:
19+
ui:
20+
inline:
21+
editorOptions:
22+
textLanguages: true
23+
```
24+
Or set specific properties:
25+
```yaml
26+
'Neos.Demo:Content.Text':
27+
properties:
28+
text:
29+
ui:
30+
inline:
31+
editorOptions:
32+
textLanguages:
33+
languages:
34+
de: German
35+
en: My.Package:Main:textLanguages.languages.en
36+
fr: French
37+
gr: ~
38+
placeholder: My.Package:Main:textLanguages.placeholder
39+
```
40+
41+
## Configuration
42+
By default, this plugin provides the system-languages configured in `Neos.Neos.userInterface.availableLanguages`, but different default values can be set at `Neos.Neos.Ui.frontendConfiguration.'Prgfx.Neos.TextPartLanguage:languages'`.
43+
44+
## Appearance
45+
In the backend this plugin underlines all marked text-elements through a stylesheet included in `prototype(Neos.Neos:Page).head.stylesheets.textPartLanguage` which you might disable, if you don't require it or want to apply custom styling.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
prototype(Neos.Neos:Page) {
2+
head {
3+
stylesheets {
4+
textPartLanguage = Neos.Fusion:Tag {
5+
tagName = 'link'
6+
attributes {
7+
rel = 'stylesheet'
8+
href = Neos.Fusion:ResourceUri {
9+
path = 'resource://Prgfx.Neos.TextPartLanguage/Public/Css/TextPartLanguage/Styles.css'
10+
}
11+
}
12+
@if.inBackend = ${documentNode.context.inBackend}
13+
}
14+
}
15+
}
16+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"compilerOptions": {
3+
"experimentalDecorators": true
4+
}
5+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"description": "",
3+
"license": "GNU GPLv3",
4+
"private": true,
5+
"scripts": {
6+
"build": "neos-react-scripts build",
7+
"watch": "neos-react-scripts watch"
8+
},
9+
"devDependencies": {
10+
"@neos-project/neos-ui-extensibility": "*"
11+
},
12+
"neos": {
13+
"buildTargetDirectory": "../../../Public/JavaScript/TextPartLanguage"
14+
},
15+
"dependencies": {
16+
"@ckeditor/ckeditor5-language": "27.1.0",
17+
"@ckeditor/ckeditor5-utils": "^27.1.0",
18+
"null-loader": "^4.0.1",
19+
"raw-loader": "^4.0.2"
20+
}
21+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { PureComponent } from 'react';
2+
import PropTypes from 'prop-types';
3+
import { neos } from '@neos-project/neos-ui-decorators';
4+
import { SelectBox } from '@neos-project/react-ui-components';
5+
import { connect } from 'react-redux';
6+
import { $transform } from 'plow-js';
7+
import { selectors } from '@neos-project/neos-ui-redux-store';
8+
import * as CkEditorApi from '@neos-project/neos-ui-ckeditor5-bindings';
9+
import { commandName } from './command';
10+
11+
export const sanitizeOptions = (options) =>
12+
Object.entries(options || {}).filter(tpl => !!tpl[1]);
13+
14+
@neos(globalRegistry => ({
15+
i18nRegistry: globalRegistry.get('i18n'),
16+
}))
17+
@connect($transform({
18+
formattingUnderCursor: selectors.UI.ContentCanvas.formattingUnderCursor,
19+
}))
20+
export default class LanguageSelect extends PureComponent {
21+
static propTypes = {
22+
formattingUnderCursor: PropTypes.object.isRequired,
23+
inlineEditorOptions: PropTypes.object.isRequired,
24+
i18nRegistry: PropTypes.object.isRequired,
25+
defaultLanguages: PropTypes.object,
26+
}
27+
28+
constructor(props) {
29+
super(props);
30+
this.handleOnSelect = this.handleOnSelect.bind(this);
31+
}
32+
33+
render() {
34+
const options = sanitizeOptions(
35+
this.props.inlineEditorOptions.textLanguages.languages
36+
|| this.props.defaultLanguages
37+
|| {}
38+
)
39+
.map(([value, label]) => ({
40+
label: this.props.i18nRegistry.translate(label),
41+
value,
42+
}));
43+
const placeholderKey = this.props.inlineEditorOptions.textLanguages.placeholder
44+
|| 'Prgfx.Neos.TextPartLanguage:Editor:placeholder';
45+
const placeholder = this.props.i18nRegistry.translate(placeholderKey);
46+
const currentValue = this.props.formattingUnderCursor.textPartLanguage || null;
47+
return (
48+
<SelectBox
49+
options={options}
50+
value={currentValue}
51+
placeholder={placeholder}
52+
allowEmpty
53+
onValueChange={this.handleOnSelect}
54+
/>
55+
);
56+
}
57+
58+
handleOnSelect(languageCode) {
59+
CkEditorApi.executeCommand(commandName, {
60+
languageCode,
61+
});
62+
}
63+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { Command } from 'ckeditor5-exports';
2+
import { attributeName } from './editing';
3+
4+
export const commandName = 'textPartLanguage';
5+
6+
export default class TextPartLanguageCommand extends Command {
7+
refresh() {
8+
const model = this.editor.model;
9+
const doc = model.document;
10+
11+
this.value = this._getValueFromFirstAllowedNode();
12+
this.isEnabled = model.schema.checkAttributeInSelection(doc.selection, attributeName);
13+
}
14+
15+
execute({ languageCode } = {}) {
16+
const model = this.editor.model;
17+
const doc = model.document;
18+
const selection = doc.selection;
19+
20+
const value = languageCode || false;
21+
22+
model.change(writer => {
23+
if (selection.isCollapsed) {
24+
// from the original plugin:
25+
// this seems to apply to an "empty" selection and would just
26+
// insert span at the current cursor, which is not what we want,
27+
// because it is difficult to find and un-set
28+
// if (value) {
29+
// writer.setSelectionAttribute(attributeName, value);
30+
// } else {
31+
// writer.removeSelectionAttribute(attributeName);
32+
// }
33+
} else {
34+
const ranges = model.schema.getValidRanges(selection.getRanges(), attributeName);
35+
36+
for (const range of ranges) {
37+
if (value) {
38+
writer.setAttribute(attributeName, value, range);
39+
} else {
40+
writer.removeAttribute(attributeName, range);
41+
}
42+
}
43+
}
44+
});
45+
}
46+
47+
_getValueFromFirstAllowedNode() {
48+
const model = this.editor.model;
49+
const schema = model.schema;
50+
const selection = model.document.selection;
51+
52+
if (selection.isCollapsed) {
53+
return selection.getAttribute(attributeName) || false;
54+
}
55+
56+
for (const range of selection.getRanges()) {
57+
for (const item of range.getItems()) {
58+
if (schema.checkAttribute(item, attributeName)) {
59+
return item.getAttribute(attributeName) || false;
60+
}
61+
}
62+
}
63+
64+
return false;
65+
}
66+
67+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Plugin } from 'ckeditor5-exports';
2+
import TextPartLanguageCommand, { commandName } from './command';
3+
4+
export const attributeName = 'language';
5+
6+
export default class TextPartLanguageEditing extends Plugin {
7+
static get pluginName() {
8+
return 'TextPartLanguageEditing';
9+
}
10+
11+
constructor(editor) {
12+
super(editor);
13+
editor.config.define('language', {
14+
textPartLanguage: []
15+
});
16+
}
17+
18+
init() {
19+
const editor = this.editor;
20+
21+
editor.model.schema.extend('$text', { allowAttributes: attributeName });
22+
editor.model.schema.setAttributeProperties(attributeName, {
23+
copyOnEnter: true
24+
});
25+
this._defineConverters();
26+
editor.commands.add(commandName, new TextPartLanguageCommand(editor));
27+
}
28+
29+
_defineConverters() {
30+
const conversion = this.editor.conversion;
31+
32+
conversion.for('upcast').elementToAttribute({
33+
model: {
34+
key: attributeName,
35+
value: viewElement => viewElement.getAttribute('lang')
36+
},
37+
view: {
38+
name: 'span',
39+
attributes: { lang: /[\s\S]+/ }
40+
}
41+
});
42+
43+
conversion.for('downcast').attributeToElement({
44+
model: attributeName,
45+
view: (attributeValue, writer) => {
46+
if (!attributeValue) {
47+
return;
48+
}
49+
50+
return writer.createAttributeElement('span', {
51+
lang: attributeValue,
52+
});
53+
}
54+
});
55+
}
56+
}

0 commit comments

Comments
 (0)