diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 1594646d8..11090ce4b 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -7,6 +7,7 @@ import {getPlugin as getCodeeditorPlugin} from "./plugin-codeeditor"; import {getPlugin as getDrawioPlugin} from "./plugin-drawio"; import {getPlugin as getCustomhrPlugin} from "./plugins-customhr"; import {getPlugin as getImagemanagerPlugin} from "./plugins-imagemanager"; +import {getPlugin as getAboutPlugin} from "./plugins-about"; const style_formats = [ {title: "Large Header", format: "h2", preview: 'color: blue;'}, @@ -74,7 +75,7 @@ function buildToolbar(options) { 'bullist numlist outdent indent', textDirPlugins, 'table imagemanager-insert link hr codeeditor drawio media', - 'removeformat code ${textDirPlugins} fullscreen' + 'removeformat code about fullscreen' ]; return toolbar.filter(row => Boolean(row)).join(' | '); @@ -100,12 +101,14 @@ function gatherPlugins(options) { "codeeditor", "media", "imagemanager", + "about", options.textDirection === 'rtl' ? 'directionality' : '', ]; window.tinymce.PluginManager.add('codeeditor', getCodeeditorPlugin(options)); window.tinymce.PluginManager.add('customhr', getCustomhrPlugin(options)); window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin(options)); + window.tinymce.PluginManager.add('about', getAboutPlugin(options)); if (options.drawioUrl) { window.tinymce.PluginManager.add('drawio', getDrawioPlugin(options)); diff --git a/resources/js/wysiwyg/plugins-about.js b/resources/js/wysiwyg/plugins-about.js new file mode 100644 index 000000000..1585de72d --- /dev/null +++ b/resources/js/wysiwyg/plugins-about.js @@ -0,0 +1,29 @@ +/** + * @param {Editor} editor + * @param {String} url + */ +function register(editor, url) { + + const aboutDialog = { + title: 'About the WYSIWYG Editor', + url: window.baseUrl('/help/wysiwyg'), + }; + + editor.ui.registry.addButton('about', { + icon: 'help', + tooltip: 'About the editor', + onAction() { + tinymce.activeEditor.windowManager.openUrl(aboutDialog); + } + }); + +} + + +/** + * @param {WysiwygConfigOptions} options + * @return {register} + */ +export function getPlugin(options) { + return register; +} \ No newline at end of file diff --git a/resources/lang/en/editor.php b/resources/lang/en/editor.php index 83e8f4313..ed40133e4 100644 --- a/resources/lang/en/editor.php +++ b/resources/lang/en/editor.php @@ -130,4 +130,17 @@ return [ 'open_link' => 'Open link in...', 'open_link_current' => 'Current window', 'open_link_new' => 'New window', + + // About view + 'editor_license' => 'Editor License & Copyright', + 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under an LGPLv2.1 license.', + 'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.', + 'save_continue' => 'Save Page & Continue', + 'callouts_cycle' => '(Keep pressing to toggle through types)', + 'shortcuts' => 'Shortcuts', + 'shortcut' => 'Shortcut', + 'shortcuts_intro' => 'The following shortcuts are available in the editor:', + 'windows_linux' => '(Windows/Linux)', + 'mac' => '(Mac)', + 'description' => 'Description', ]; diff --git a/resources/views/errors/503.blade.php b/resources/views/errors/503.blade.php index acf588f4a..d44c45e62 100644 --- a/resources/views/errors/503.blade.php +++ b/resources/views/errors/503.blade.php @@ -1,22 +1,6 @@ -<!DOCTYPE html> -<html lang="{{ config('app.lang') }}" - dir="{{ config('app.rtl') ? 'rtl' : 'ltr' }}"> -<head> - <title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}</title> +@extends('layouts.plain') - <!-- Meta --> - <meta name="viewport" content="width=device-width"> - <meta charset="utf-8"> - - <!-- Styles and Fonts --> - <link rel="stylesheet" href="{{ versioned_asset('dist/styles.css') }}"> - <link rel="stylesheet" media="print" href="{{ versioned_asset('dist/print-styles.css') }}"> - - <!-- Custom Styles & Head Content --> - @include('common.custom-styles') - @include('common.custom-head') -</head> -<body> +@section('content') <div id="content" class="block"> <div class="container small mt-xl"> <div class="card content-wrap auto-height"> @@ -25,5 +9,4 @@ </div> </div> </div> -</body> -</html> +@endsection \ No newline at end of file diff --git a/resources/views/help/wysiwyg.blade.php b/resources/views/help/wysiwyg.blade.php new file mode 100644 index 000000000..8d6b0b8e7 --- /dev/null +++ b/resources/views/help/wysiwyg.blade.php @@ -0,0 +1,122 @@ +@extends('layouts.plain') + +@section('content') + <div class="px-l pb-m m-s card"> + + <h4>{{ trans('editor.editor_license') }}</h4> + <p> + {!! trans('editor.editor_tiny_license', ['tinyLink' => '<a href="https://www.tiny.cloud/" target="_blank" rel="noopener noreferrer">TinyMCE</a>']) !!} + <br> + <a href="{{ url('/libs/tinymce/license.txt') }}" target="_blank">{{ trans('editor.editor_tiny_license_link') }}</a> + </p> + + <h4>{{ trans('editor.shortcuts') }}</h4> + + <p>{{ trans('editor.shortcuts_intro') }}</p> + <table> + <thead> + <tr> + <th>{{ trans('editor.shortcut') }} {{ trans('editor.windows_linux') }}</th> + <th>{{ trans('editor.shortcut') }} {{ trans('editor.mac') }}</th> + <th>{{ trans('editor.description') }}</th> + </tr> + </thead> + <tbody> + <tr> + <td><code>Ctrl</code>+<code>S</code></td> + <td><code>Cmd</code>+<code>S</code></td> + <td>{{ trans('entities.pages_edit_save_draft') }}</td> + </tr> + <tr> + <td><code>Ctrl</code>+<code>Enter</code></td> + <td><code>Cmd</code>+<code>Enter</code></td> + <td>{{ trans('editor.save_continue') }}</td> + </tr> + <tr> + <td><code>Ctrl</code>+<code>B</code></td> + <td><code>Cmd</code>+<code>B</code></td> + <td>{{ trans('editor.bold') }}</td> + </tr> + <tr> + <td><code>Ctrl</code>+<code>I</code></td> + <td><code>Cmd</code>+<code>I</code></td> + <td>{{ trans('editor.italic') }}</td> + </tr> + <tr> + <td> + <code>Ctrl</code>+<code>1</code><br> + <code>Ctrl</code>+<code>2</code><br> + <code>Ctrl</code>+<code>3</code><br> + <code>Ctrl</code>+<code>4</code> + </td> + <td> + <code>Cmd</code>+<code>1</code><br> + <code>Cmd</code>+<code>2</code><br> + <code>Cmd</code>+<code>3</code><br> + <code>Cmd</code>+<code>4</code> + </td> + <td> + {{ trans('editor.header_large') }} <br> + {{ trans('editor.header_medium') }} <br> + {{ trans('editor.header_small') }} <br> + {{ trans('editor.header_tiny') }} + </td> + </tr> + <tr> + <td> + <code>Ctrl</code>+<code>5</code><br> + <code>Ctrl</code>+<code>D</code> + </td> + <td> + <code>Cmd</code>+<code>5</code><br> + <code>Cmd</code>+<code>D</code> + </td> + <td>{{ trans('editor.paragraph') }}</td> + </tr> + <tr> + <td> + <code>Ctrl</code>+<code>6</code><br> + <code>Ctrl</code>+<code>Q</code> + </td> + <td> + <code>Cmd</code>+<code>6</code><br> + <code>Cmd</code>+<code>Q</code> + </td> + <td>{{ trans('editor.blockquote') }}</td> + </tr> + <tr> + <td> + <code>Ctrl</code>+<code>7</code><br> + <code>Ctrl</code>+<code>E</code> + </td> + <td> + <code>Cmd</code>+<code>7</code><br> + <code>Cmd</code>+<code>E</code> + </td> + <td>{{ trans('editor.insert_code_block') }}</td> + </tr> + <tr> + <td> + <code>Ctrl</code>+<code>Shift</code>+<code>8</code><br> + <code>Ctrl</code>+<code>Shift</code>+<code>E</code> + </td> + <td> + <code>Cmd</code>+<code>Shift</code>+<code>8</code><br> + <code>Cmd</code>+<code>Shift</code>+<code>E</code> + </td> + <td>{{ trans('editor.inline_code') }}</td> + </tr> + <tr> + <td><code>Ctrl</code>+<code>9</code></td> + <td><code>Cmd</code>+<code>9</code></td> + <td> + {{ trans('editor.callouts') }} <br> + {{ trans('editor.callouts_cycle') }} + </td> + </tr> + </tbody> + </table> + + </div> +@endsection + diff --git a/resources/views/layouts/plain.blade.php b/resources/views/layouts/plain.blade.php new file mode 100644 index 000000000..588bf5b7c --- /dev/null +++ b/resources/views/layouts/plain.blade.php @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="{{ config('app.lang') }}" + dir="{{ config('app.rtl') ? 'rtl' : 'ltr' }}"> +<head> + <title>{{ isset($pageTitle) ? $pageTitle . ' | ' : '' }}{{ setting('app-name') }}</title> + + <!-- Meta --> + <meta name="viewport" content="width=device-width"> + <meta charset="utf-8"> + + <!-- Styles and Fonts --> + <link rel="stylesheet" href="{{ versioned_asset('dist/styles.css') }}"> + <link rel="stylesheet" media="print" href="{{ versioned_asset('dist/print-styles.css') }}"> + + <!-- Custom Styles & Head Content --> + @include('common.custom-styles') + @include('common.custom-head') +</head> +<body> + @yield('content') +</body> +</html> diff --git a/routes/web.php b/routes/web.php index 73cc3dc66..7a12acc31 100644 --- a/routes/web.php +++ b/routes/web.php @@ -322,12 +322,15 @@ Route::get('/oidc/callback', [Auth\OidcController::class, 'callback']); Route::get('/register/invite/{token}', [Auth\UserInviteController::class, 'showSetPassword']); Route::post('/register/invite/{token}', [Auth\UserInviteController::class, 'setPassword']); -// Password reset link request routes... +// Password reset link request routes Route::get('/password/email', [Auth\ForgotPasswordController::class, 'showLinkRequestForm']); Route::post('/password/email', [Auth\ForgotPasswordController::class, 'sendResetLinkEmail']); -// Password reset routes... +// Password reset routes Route::get('/password/reset/{token}', [Auth\ResetPasswordController::class, 'showResetForm']); Route::post('/password/reset', [Auth\ResetPasswordController::class, 'reset']); +// Metadata routes +Route::view('/help/wysiwyg', 'help.wysiwyg'); + Route::fallback([HomeController::class, 'notFound'])->name('fallback'); diff --git a/tests/HelpTest.php b/tests/HelpTest.php new file mode 100644 index 000000000..2e08abfbc --- /dev/null +++ b/tests/HelpTest.php @@ -0,0 +1,25 @@ +<?php + +namespace Tests; + +class HelpTest extends TestCase +{ + + public function test_wysiwyg_help_shows_tiny_and_tiny_license_link() + { + $resp = $this->get('/help/wysiwyg'); + $resp->assertOk(); + $resp->assertElementExists('a[href="https://www.tiny.cloud/"]'); + $resp->assertElementExists('a[href="' . url('/libs/tinymce/license.txt') . '"]'); + } + + public function test_tiny_license_exists_where_expected() + { + $expectedPath = public_path('/libs/tinymce/license.txt'); + $this->assertTrue(file_exists($expectedPath)); + + $contents = file_get_contents($expectedPath); + $this->assertStringContainsString('GNU LESSER GENERAL PUBLIC LICENSE', $contents); + } + +}