Skip to content

Commit 7a550bb

Browse files
committed
[FEATURE] Add optimize button to CKEditor
DeepL Write brings lots of features optimizing text. These will help improving text beside a pre-defined translation/optimization. Add a basic functionality for implementing CKeditor optimization button and add a skeleton for talking with the backend.
1 parent 2aeae81 commit 7a550bb

9 files changed

Lines changed: 304 additions & 5 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace WebVision\DeeplWrite\Controller;
6+
7+
use Psr\Http\Message\ResponseFactoryInterface;
8+
use Psr\Http\Message\ResponseInterface;
9+
use Psr\Http\Message\ServerRequestInterface;
10+
use TYPO3\CMS\Core\Utility\GeneralUtility;
11+
use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextFactory;
12+
use TYPO3\CMS\Fluid\View\StandaloneView;
13+
use WebVision\DeeplWrite\Configuration\ConfigurationInterface;
14+
use WebVision\DeeplWrite\Domain\Enum\RephraseToneDeepL;
15+
use WebVision\DeeplWrite\Domain\Enum\RephraseWritingStyleDeepL;
16+
use WebVision\DeeplWrite\Service\DeeplService;
17+
use WebVision\DeeplWrite\Service\HtmlParser;
18+
19+
/**
20+
* @internal
21+
* This class is meant to be used within the DeepL write extension and therefore
22+
* no public API. Endpoints can change without further information.
23+
*/
24+
final class CkEditorController
25+
{
26+
public function __construct(
27+
private readonly ResponseFactoryInterface $responseFactory,
28+
private readonly ConfigurationInterface $configuration,
29+
private readonly DeeplService $deeplService,
30+
private readonly HtmlParser $htmlParser,
31+
) {
32+
}
33+
34+
public function deeplConfiguredAction(ServerRequestInterface $request): ResponseInterface
35+
{
36+
$configured = true;
37+
if ($this->configuration->getApiKey() === '') {
38+
$configured = false;
39+
}
40+
$response = $this->responseFactory->createResponse()
41+
->withHeader('Content-Type', 'application/json; charset=utf-8');
42+
$response->getBody()->write(
43+
json_encode(['configured' => $configured], JSON_THROW_ON_ERROR),
44+
);
45+
return $response;
46+
}
47+
48+
public function optimizeTextAction(ServerRequestInterface $request): ResponseInterface
49+
{
50+
$data = $request->getParsedBody();
51+
$splittedText = $this->htmlParser->splitHtml($data['text']);
52+
foreach ($splittedText as $node => $text) {
53+
$optimizedText = $this->deeplService->rephraseText(
54+
$data['text'],
55+
null,
56+
RephraseWritingStyleDeepL::tryFrom($data['style']),
57+
RephraseToneDeepL::tryFrom($data['tone'])
58+
);
59+
$splittedText[$node] = $optimizedText;
60+
}
61+
$response = $this->responseFactory->createResponse()
62+
->withHeader('Content-Type', 'application/json; charset=utf-8');
63+
$response->getBody()->write(
64+
json_encode(['result' => $this->htmlParser->buildHtml($splittedText)], JSON_THROW_ON_ERROR),
65+
);
66+
return $response;
67+
}
68+
69+
public function getEditMaskAction(ServerRequestInterface $request): ResponseInterface
70+
{
71+
$renderingContext = GeneralUtility::makeInstance(RenderingContextFactory::class)->create(
72+
templatePathsArray: [
73+
'templateRootPaths' => ['EXT:deepl_write/Resources/Private/Backend/Templates/'],
74+
]
75+
);
76+
$renderingContext->setRequest($request);
77+
$renderingContext->setControllerAction('Edit');
78+
$renderingContext->setControllerName('CkEditor');
79+
$view = GeneralUtility::makeInstance(StandaloneView::class, $renderingContext);
80+
$view->assignMultiple([
81+
'styles' => RephraseWritingStyleDeepL::cases(),
82+
'tones' => RephraseToneDeepL::cases(),
83+
]);
84+
$response = $this->responseFactory->createResponse()
85+
->withHeader('Content-Type', 'text/html; charset=utf-8');
86+
$response->getBody()->write($view->render());
87+
return $response;
88+
}
89+
}

Classes/Service/DeeplService.php

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function __construct(
3636
*/
3737
public function rephraseText(
3838
string $text,
39-
string $targetLanguage,
39+
?string $targetLanguage = null,
4040
?RephraseWritingStyleDeepL $writingStyle = null,
4141
?RephraseToneDeepL $tone = null
4242
): string {
@@ -47,10 +47,22 @@ public function rephraseText(
4747
1741344565
4848
);
4949
}
50-
if ($writingStyle instanceof RephraseWritingStyleDeepL && RephraseSupportedDeepLLanguage::isWritingStyleSupported($targetLanguage)) {
50+
if (
51+
$writingStyle instanceof RephraseWritingStyleDeepL
52+
&& (
53+
$targetLanguage === null ||
54+
RephraseSupportedDeepLLanguage::isWritingStyleSupported($targetLanguage)
55+
)
56+
) {
5157
$options[RephraseTextOptions::WRITING_STYLE] = $writingStyle->value;
5258
}
53-
if ($tone instanceof RephraseToneDeepL && RephraseSupportedDeepLLanguage::isToneSupportedByLanguage($targetLanguage)) {
59+
if (
60+
$tone instanceof RephraseToneDeepL
61+
&& (
62+
$targetLanguage === null ||
63+
RephraseSupportedDeepLLanguage::isToneSupportedByLanguage($targetLanguage)
64+
)
65+
) {
5466
$options[RephraseTextOptions::TONE] = $tone->value;
5567
}
5668

@@ -72,7 +84,7 @@ public function rephraseText(
7284
*/
7385
private function optimizeText(
7486
string $text,
75-
string $targetLanguage,
87+
?string $targetLanguage = null,
7688
array $options = []
7789
): string {
7890
$rephrased = $this->deeplClient->rephraseText(
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
use WebVision\DeeplWrite\Controller\CkEditorController;
4+
5+
return [
6+
'deeplwrite_ckeditor_configuration' => [
7+
'path' => '/deepl-write/ckeditor/configuration',
8+
'target' => CkEditorController::class . '::deeplConfiguredAction',
9+
'methods' => ['GET'],
10+
],
11+
'deeplwrite_ckeditor_optimize' => [
12+
'path' => '/deepl-write/ckeditor/optimize',
13+
'target' => CkEditorController::class . '::optimizeTextAction',
14+
'methods' => ['POST'],
15+
],
16+
'deeplwrite_ckeditor_edit' => [
17+
'path' => '/deepl-write/ckeditor/edit',
18+
'target' => CkEditorController::class . '::getEditMaskAction',
19+
'methods' => ['GET'],
20+
]
21+
];
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
return [
4+
'dependencies' => ['backend'],
5+
'tags' => [
6+
'backend.form',
7+
],
8+
'imports' => [
9+
'@web-vision/deepl-write/deeplwrite-plugin.js' => 'EXT:deepl_write/Resources/Public/JavaScript/Ckeditor/deeplwrite-plugin.js',
10+
],
11+
];
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
editor:
2+
config:
3+
importModules:
4+
- { module: '@web-vision/deepl-write/deeplwrite-plugin.js', exports: [ 'Deeplwrite' ] }
5+
toolbar:
6+
items:
7+
- bold
8+
- italic
9+
- '|'
10+
- clipboard
11+
- undo
12+
- redo
13+
- '|'
14+
- deeplwrite

Configuration/Services.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ services:
1010
WebVision\DeeplWrite\Hooks\WriteHook:
1111
public: true
1212

13+
WebVision\DeeplWrite\Controller\CkEditorController:
14+
public: true
15+
1316
WebVision\DeeplWrite\Configuration\ConfigurationInterface:
1417
class: WebVision\DeeplWrite\Configuration\Configuration
1518

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<html
2+
xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers"
3+
xmlns:core="http://typo3.org/ns/TYPO3/CMS/Core/ViewHelpers"
4+
xmlns:deeplWrite="http://typo3.org/ns/WebVision/DeeplWrite/ViewHelpers"
5+
data-namespace-typo3-fluid="true"
6+
>
7+
<div class="col-md-12">
8+
<div class="row">
9+
<fieldset class="form-section col-md-6 col-xs-12">
10+
<h3 class="form-section-headline">Style</h3>
11+
<f:for each="{styles}" as="style" iteration="i">
12+
<input type="radio" value="{style.value}" name="format" class="style btn-check" id="style-{i.index}">
13+
<label class="btn btn-default" for="style-{i.index}">{style.name}</label>
14+
</f:for>
15+
</fieldset>
16+
<fieldset class="form-section col-md-6 col-xs-12">
17+
<h3 class="form-section-headline">Tone</h3>
18+
<f:for each="{tones}" as="tone" iteration="i">
19+
<input type="radio" value="{tone.value}" name="format" class="tone btn-check" id="tone-{i.index}">
20+
<label class="btn btn-default" for="tone-{i.index}">{tone.name}</label>
21+
</f:for>
22+
</fieldset>
23+
</div>
24+
<div class="row">
25+
<div class="col-md-6 col-xs-12">
26+
<label for="original" class="form-label t3js-formengine-label">Original</label>
27+
<textarea rows="20" class="form-control t3js-formengine-textarea formengine-textarea" id="original"></textarea>
28+
</div>
29+
<div class="col-md-6 col-xs-12">
30+
<label for="optimized" class="form-label t3js-formengine-label">Optimized</label>
31+
<textarea rows="20" class="form-control t3js-formengine-textarea formengine-textarea" id="optimized"></textarea>
32+
</div>
33+
</div>
34+
</div>
35+
</html>
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {Plugin} from '@ckeditor/ckeditor5-core';
2+
import {ButtonView} from '@ckeditor/ckeditor5-ui';
3+
import AjaxRequest from '@typo3/core/ajax/ajax-request.js';
4+
import Modal from '@typo3/backend/modal.js';
5+
6+
export class Deeplwrite extends Plugin {
7+
static pluginName = 'Deeplwrite';
8+
9+
init() {
10+
const editor = this.editor;
11+
12+
// The button must be registered among the UI components of the editor
13+
// to be displayed in the toolbar.
14+
editor.ui.componentFactory.add(Deeplwrite.pluginName, () => {
15+
// The button will be an instance of ButtonView.
16+
const button = new ButtonView();
17+
18+
button.set({
19+
label: 'DeepL Write',
20+
withText: true,
21+
icon: '',
22+
isEnabled: false
23+
});
24+
25+
// Execute a callback function when the button is clicked
26+
button.on('execute', () => {
27+
new AjaxRequest(TYPO3.settings.ajaxUrls.deeplwrite_ckeditor_edit)
28+
.get()
29+
.then(async function (response) {
30+
const deeplConfiguration = await response.resolve();
31+
const content = document.createElement('div');
32+
content.innerHTML = deeplConfiguration;
33+
content.querySelector('#original').value = editor.getData();
34+
const optimizeModal = Modal.advanced({
35+
content: content,
36+
size: Modal.sizes.large,
37+
title: 'Optimize text with DeepL Write',
38+
staticBackdrop: true,
39+
buttons: [
40+
{
41+
text: 'Optimize text',
42+
name: 'optimize',
43+
icon: 'actions-lightbulb-on',
44+
active: false,
45+
btnClass: 'btn-primary',
46+
trigger: function(event, optimizeModal) {
47+
const format = content.querySelector('input[name="format"]:checked');
48+
let style = '';
49+
let tone = '';
50+
console.log(format);
51+
if (format !== null) {
52+
if (format.classList.contains('style')) {
53+
style = format.value;
54+
} else {
55+
tone = format.value;
56+
}
57+
}
58+
new AjaxRequest(TYPO3.settings.ajaxUrls.deeplwrite_ckeditor_optimize)
59+
.post({
60+
text: editor.getData(),
61+
style: style,
62+
tone: tone
63+
})
64+
.then(async function (response){
65+
const value = await response.resolve();
66+
content.querySelector('#optimized').value = value.result;
67+
})
68+
}
69+
},
70+
{
71+
text: 'Save changes',
72+
name: 'save',
73+
icon: 'actions-document-save',
74+
active: false,
75+
btnClass: 'btn-secondary',
76+
trigger: function(event, optimizeModal) {
77+
const optimizedText = content.querySelector('#optimized').value;
78+
if (optimizedText.length > 0) {
79+
editor.setData(optimizedText);
80+
}
81+
optimizeModal.hideModal();
82+
}
83+
}
84+
]
85+
})
86+
});
87+
88+
89+
// // Change the model using the model writer
90+
// editor.model.change(writer => {
91+
//
92+
// // Insert the text at the user's current position
93+
// editor.model.insertContent(writer.createText(now.toString()));
94+
// });
95+
});
96+
97+
new AjaxRequest(TYPO3.settings.ajaxUrls.deeplwrite_ckeditor_configuration)
98+
.get()
99+
.then(async function (response) {
100+
const deeplConfiguration = await response.resolve();
101+
if (deeplConfiguration.configured === true) {
102+
button.set('isEnabled', true);
103+
}
104+
});
105+
106+
return button;
107+
});
108+
}
109+
}

ext_localconf.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
<?php
22

3+
use WebVision\DeeplWrite\Hooks\WriteHook;
4+
35
(static function (): void {
46
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass']['deeplWrite'] =
5-
\WebVision\DeeplWrite\Hooks\WriteHook::class;
7+
WriteHook::class;
8+
9+
$GLOBALS['TYPO3_CONF_VARS']['RTE']['Presets']['deeplwrite']
10+
= 'EXT:deepl_write/Configuration/RTE/DeeplWritePreset.yaml';
611
})();

0 commit comments

Comments
 (0)