'about' => 'About the editor',
'about_title' => 'About the WYSIWYG Editor',
'editor_license' => 'Editor License & Copyright',
+ 'editor_lexical_license' => 'This editor is built as a fork of :lexicalLink which is distributed under the MIT license.',
+ 'editor_lexical_license_link' => 'Full license details can be found here.',
'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.',
'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.',
'save_continue' => 'Save Page & Continue',
function register(editor) {
const aboutDialog = {
title: 'About the WYSIWYG Editor',
- url: window.baseUrl('/help/wysiwyg'),
+ url: window.baseUrl('/help/tinymce'),
};
editor.ui.registry.addButton('about', {
} from "lexical";
import redoIcon from "@icons/editor/redo.svg";
import sourceIcon from "@icons/editor/source-view.svg";
-import {getEditorContentAsHtml} from "../../../utils/actions";
import fullscreenIcon from "@icons/editor/fullscreen.svg";
+import aboutIcon from "@icons/editor/about.svg";
+import {getEditorContentAsHtml} from "../../../utils/actions";
export const undo: EditorButtonDefinition = {
label: 'Undo',
isActive(selection, context: EditorUiContext) {
return context.containerDOM.classList.contains('fullscreen');
}
+};
+
+export const about: EditorButtonDefinition = {
+ label: 'About the editor',
+ icon: aboutIcon,
+ async action(context: EditorUiContext, button: EditorButton) {
+ const modal = context.manager.createModal('about');
+ modal.show({});
+ },
+ isActive(selection, context: EditorUiContext) {
+ return false;
+ }
};
\ No newline at end of file
import {EditorFormDefinition} from "../../framework/forms";
-import {EditorUiContext} from "../../framework/core";
+import {EditorUiContext, EditorUiElement} from "../../framework/core";
import {setEditorContentFromHtml} from "../../../utils/actions";
+import {ExternalContent} from "../../framework/blocks/external-content";
export const source: EditorFormDefinition = {
submitText: 'Save',
type: 'textarea',
},
],
+};
+
+export const about: EditorFormDefinition = {
+ submitText: 'Close',
+ async action() {
+ return true;
+ },
+ fields: [
+ {
+ build(): EditorUiElement {
+ return new ExternalContent('/help/wysiwyg');
+ }
+ }
+ ],
};
\ No newline at end of file
import {EditorFormModalDefinition} from "../framework/modals";
import {details, image, link, media} from "./forms/objects";
-import {source} from "./forms/controls";
+import {about, source} from "./forms/controls";
import {cellProperties, rowProperties, tableProperties} from "./forms/tables";
export const modals: Record<string, EditorFormModalDefinition> = {
details: {
title: 'Edit collapsible block',
form: details,
+ },
+ about: {
+ title: 'About the WYSIWYG Editor',
+ form: about,
}
};
\ No newline at end of file
-import {EditorButton} from "./framework/buttons";
-import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiContext, EditorUiElement} from "./framework/core";
-import {EditorFormatMenu} from "./framework/blocks/format-menu";
-import {FormatPreviewButton} from "./framework/blocks/format-preview-button";
-import {EditorDropdownButton} from "./framework/blocks/dropdown-button";
-import {EditorColorPicker} from "./framework/blocks/color-picker";
-import {EditorTableCreator} from "./framework/blocks/table-creator";
-import {EditorColorButton} from "./framework/blocks/color-button";
-import {EditorOverflowContainer} from "./framework/blocks/overflow-container";
+import {EditorButton} from "../framework/buttons";
+import {EditorContainerUiElement, EditorSimpleClassContainer, EditorUiContext, EditorUiElement} from "../framework/core";
+import {EditorFormatMenu} from "../framework/blocks/format-menu";
+import {FormatPreviewButton} from "../framework/blocks/format-preview-button";
+import {EditorDropdownButton} from "../framework/blocks/dropdown-button";
+import {EditorColorPicker} from "../framework/blocks/color-picker";
+import {EditorTableCreator} from "../framework/blocks/table-creator";
+import {EditorColorButton} from "../framework/blocks/color-button";
+import {EditorOverflowContainer} from "../framework/blocks/overflow-container";
import {
cellProperties, clearTableFormatting,
copyColumn,
rowProperties,
splitCell,
table, tableProperties
-} from "./defaults/buttons/tables";
-import {fullscreen, redo, source, undo} from "./defaults/buttons/controls";
+} from "./buttons/tables";
+import {about, fullscreen, redo, source, undo} from "./buttons/controls";
import {
blockquote, dangerCallout,
h2,
paragraph,
successCallout,
warningCallout
-} from "./defaults/buttons/block-formats";
+} from "./buttons/block-formats";
import {
bold, clearFormating, code,
highlightColor,
superscript,
textColor,
underline
-} from "./defaults/buttons/inline-formats";
+} from "./buttons/inline-formats";
import {
alignCenter,
alignJustify,
alignRight,
directionLTR,
directionRTL
-} from "./defaults/buttons/alignments";
+} from "./buttons/alignments";
import {
bulletList,
indentDecrease,
indentIncrease,
numberList,
taskList
-} from "./defaults/buttons/lists";
+} from "./buttons/lists";
import {
codeBlock,
details, detailsEditLabel, detailsToggle, detailsUnwrap,
image,
link, media,
unlink
-} from "./defaults/buttons/objects";
-import {el} from "../utils/dom";
-import {EditorButtonWithMenu} from "./framework/blocks/button-with-menu";
-import {EditorSeparator} from "./framework/blocks/separator";
+} from "./buttons/objects";
+import {el} from "../../utils/dom";
+import {EditorButtonWithMenu} from "../framework/blocks/button-with-menu";
+import {EditorSeparator} from "../framework/blocks/separator";
export function getMainEditorFullToolbar(context: EditorUiContext): EditorContainerUiElement {
// Meta elements
new EditorOverflowContainer(3, [
new EditorButton(source),
+ new EditorButton(about),
new EditorButton(fullscreen),
// Test
--- /dev/null
+import {EditorUiElement} from "../core";
+import {el} from "../../../utils/dom";
+
+export class ExternalContent extends EditorUiElement {
+
+ /**
+ * The URL for HTML to be loaded from.
+ */
+ protected url: string = '';
+
+ constructor(url: string) {
+ super();
+ this.url = url;
+ }
+
+ buildDOM(): HTMLElement {
+ const wrapper = el('div', {
+ class: 'editor-external-content',
+ });
+
+ window.$http.get(this.url).then(resp => {
+ if (typeof resp.data === 'string') {
+ wrapper.innerHTML = resp.data;
+ }
+ });
+
+ return wrapper;
+ }
+}
getImageToolbarContent,
getLinkToolbarContent,
getMainEditorFullToolbar, getTableToolbarContent
-} from "./toolbars";
+} from "./defaults/toolbars";
import {EditorUIManager} from "./framework/manager";
import {EditorUiContext} from "./framework/core";
import {CodeBlockDecorator} from "./decorators/code-block";
text-align: center;
padding: 0.2em;
}
+.editor-external-content {
+ min-width: 500px;
+ min-height: 500px;
+ h4:first-child {
+ margin-top: 0;
+ }
+}
// In-editor elements
.editor-image-wrap {
--- /dev/null
+@extends('layouts.plain')
+@section('document-class', 'bg-white ' . (setting()->getForCurrentUser('dark-mode-enabled') ? 'dark-mode ' : ''))
+
+@section('content')
+ <div class="p-m">
+
+ <h4 class="mt-s">{{ 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>8</code><br>
+ <code>Ctrl</code>+<code>Shift</code>+<code>E</code>
+ </td>
+ <td>
+ <code>Cmd</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>
+ <small>{{ trans('editor.callouts_cycle') }}</small>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <code>Ctrl</code>+<code>O</code> <br>
+ <code>Ctrl</code>+<code>P</code>
+ </td>
+ <td>
+ <code>Cmd</code>+<code>O</code> <br>
+ <code>Cmd</code>+<code>P</code>
+ </td>
+ <td>
+ {{ trans('editor.list_numbered') }} <br>
+ {{ trans('editor.list_bullet') }}
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <code>Ctrl</code>+<code>Shift</code>+<code>K</code>
+ </td>
+ <td>
+ <code>Cmd</code>+<code>Shift</code>+<code>K</code>
+ </td>
+ <td>{{ trans('editor.link_selector') }}</td>
+ </tr>
+ </tbody>
+ </table>
+
+ </div>
+@endsection
+
-@extends('layouts.plain')
-@section('document-class', 'bg-white ' . (setting()->getForCurrentUser('dark-mode-enabled') ? 'dark-mode ' : ''))
+<h4>{{ trans('editor.shortcuts') }}</h4>
-@section('content')
- <div class="p-m">
-
- <h4 class="mt-s">{{ 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>8</code><br>
- <code>Ctrl</code>+<code>Shift</code>+<code>E</code>
- </td>
- <td>
- <code>Cmd</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>
- <small>{{ trans('editor.callouts_cycle') }}</small>
- </td>
- </tr>
- <tr>
- <td>
- <code>Ctrl</code>+<code>O</code> <br>
- <code>Ctrl</code>+<code>P</code>
- </td>
- <td>
- <code>Cmd</code>+<code>O</code> <br>
- <code>Cmd</code>+<code>P</code>
- </td>
- <td>
- {{ trans('editor.list_numbered') }} <br>
- {{ trans('editor.list_bullet') }}
- </td>
- </tr>
- <tr>
- <td>
- <code>Ctrl</code>+<code>Shift</code>+<code>K</code>
- </td>
- <td>
- <code>Cmd</code>+<code>Shift</code>+<code>K</code>
- </td>
- <td>{{ trans('editor.link_selector') }}</td>
- </tr>
- </tbody>
- </table>
-
- </div>
-@endsection
+<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>8</code><br>
+ <code>Ctrl</code>+<code>Shift</code>+<code>E</code>
+ </td>
+ <td>
+ <code>Cmd</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>
+ <small>{{ trans('editor.callouts_cycle') }}</small>
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <code>Ctrl</code>+<code>O</code> <br>
+ <code>Ctrl</code>+<code>P</code>
+ </td>
+ <td>
+ <code>Cmd</code>+<code>O</code> <br>
+ <code>Cmd</code>+<code>P</code>
+ </td>
+ <td>
+ {{ trans('editor.list_numbered') }} <br>
+ {{ trans('editor.list_bullet') }}
+ </td>
+ </tr>
+ <tr>
+ <td>
+ <code>Ctrl</code>+<code>Shift</code>+<code>K</code>
+ </td>
+ <td>
+ <code>Cmd</code>+<code>Shift</code>+<code>K</code>
+ </td>
+ <td>{{ trans('editor.link_selector') }}</td>
+ </tr>
+ </tbody>
+</table>
+<h4 class="mt-s">{{ trans('editor.editor_license') }}</h4>
+<p>
+ {!! trans('editor.editor_lexical_license', ['lexicalLink' => '<a href="https://lexical.dev/" target="_blank" rel="noopener noreferrer">Lexical</a>']) !!}
+ <br>
+ <em class="text-muted">Copyright (c) Meta Platforms, Inc. and affiliates.</em>
+ <br>
+ <a href="{{ url('/licenses') }}" target="_blank">{{ trans('editor.editor_lexical_license_link') }}</a>
+</p>
\ No newline at end of file
Route::post('/password/reset', [AccessControllers\ResetPasswordController::class, 'reset'])->middleware('throttle:public');
// Metadata routes
+Route::view('/help/tinymce', 'help.tinymce');
Route::view('/help/wysiwyg', 'help.wysiwyg');
Route::fallback([MetaController::class, 'notFound'])->name('fallback');
class HelpTest extends TestCase
{
- public function test_wysiwyg_help_shows_tiny_and_tiny_license_link()
+ public function test_tinymce_help_shows_tiny_and_tiny_license_link()
{
- $resp = $this->get('/help/wysiwyg');
+ $resp = $this->get('/help/tinymce');
$resp->assertOk();
$this->withHtml($resp)->assertElementExists('a[href="https://www.tiny.cloud/"]');
$this->withHtml($resp)->assertElementExists('a[href="' . url('/libs/tinymce/license.txt') . '"]');
$contents = file_get_contents($expectedPath);
$this->assertStringContainsString('MIT License', $contents);
}
+
+ public function test_wysiwyg_help_shows_lexical_and_licenses_link()
+ {
+ $resp = $this->get('/help/wysiwyg');
+ $resp->assertOk();
+ $this->withHtml($resp)->assertElementExists('a[href="https://lexical.dev/"]');
+ $this->withHtml($resp)->assertElementExists('a[href="' . url('/licenses') . '"]');
+ }
}