Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi-root editor implementation #13532

Merged
merged 24 commits into from
Mar 1, 2023
Merged

Multi-root editor implementation #13532

merged 24 commits into from
Mar 1, 2023

Conversation

scofalik
Copy link
Contributor

@scofalik scofalik commented Feb 23, 2023

Suggested merge commit message (convention)

Feature (multi-root-editor): Introduced multi-root editor type that allows defining multiple editable areas handled by one editor instance. Closes #11493.

Feature (build-multi-root): Introduced new editor build featuring multi-root editor.

Other (core): EditorConfig#initialData and EditorConfig#placeholder can now be set to Record<string, string> where keys are root names and values are settings for related roots.


Introducing multi-root-editor editor type + a build, so two new packages.

When implementing, I run into some issues with code/class organization and types. I commented on them. Since that would require more changes that I want to do at this point, I believe that most can be left as it is. Or we may discuss it and create follow-up issues.

But one thing in particular is annoying me and it is editable field on MultiRootEditorUIView which doesn't make much sense. This is something that we can consider solving but it will require changes in other parts of the code.

Comment on lines +85 to +86
<div contenteditable="true">
<div contenteditable="false">
Copy link
Contributor Author

@scofalik scofalik Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look good but it is necessary to have proper arrows control. But maybe you know better solutions? By adding this, we don't have to introduce logic that would be fired when arrow key is pressed at the beginning or at the end of an editable. The browser handles it by itself (and caret x-position is preserved).

However, it may add some quirkiness...? OTOH, we use similar structures this inside the editor (e.g. tables).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -605,7 +605,7 @@ Note: The number of options was reduced on purpose. We understood that configuri
<td>
<p>N/A.</p>
<p>The {@link module:editor-decoupled/decouplededitor~DecoupledEditor decoupled editor} allows configuring where should the toolbar and the editable element be inserted.</p>
<p>In addition to that, CKEditor 5 Framework architecture allows for writing a custom editor that contains multiple editable elements (document roots). See the {@link examples/framework/multi-root-editor multi-root editor example}.</p>
<p>The {@link examples/builds/multi-root-editor multi-root editor} allows for creating an editor instance that contains multiple editable elements (document roots).</p>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not familiar with this CKE4 option, so I don't know if this description is correct. cc @Reinmar

Comment on lines 486 to 488
initialData?: string | Record<string, string>;
language?: string | LanguageConfig;
placeholder?: string;
placeholder?: string | Record<string, string>;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a change that I needed to make to keep the official multi-root editor API the same as what was shown in the guide (and people use) and to be consistent with the data format for multi-root editors that we already have, e.g. in CS.

Other than that, DataController#set already accepts data in this format.

However, this broadens the type, which prompted other changes. I am not sure about docs -- now we need to keep two snippet examples and explain it.

Maybe a better solution would be to have new architecture for editor configs:

  • EditorConfig -- without things that are different for multi-root editor and single-root editor,
  • SingleRootEditorConfig -- adds initialData: string and placeholder: string,
  • MultiRootEditorConfig -- adds initialData: Record<string, string> and same for placeholder.

I am not sure if this will not be even more confusing or problematic, though. For example, places that use EditorConfig now will have to know which type of config it is. OTOH, initialData and placeholder aren't used in many places in the code base. Maybe they are encapsulated inside the editor-related classes and maybe there will be no problem with recognizing what type of config interface is passed.

Copy link
Contributor Author

@scofalik scofalik Feb 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW. { main: '<p>Foo</p>' } can be used also for single-root editors. And if somehow you created a single-editor instance with different root name, this, I believe, is the only option to set it too.

All I mean is that Record<string, string> makes sense for single-roots too, which is why I am okay with having it like this.

@@ -19,7 +19,7 @@ import type { Constructor, Mixed } from '@ckeditor/ckeditor5-utils';
*/
export default function DataApiMixin<Base extends Constructor<Editor>>( base: Base ): Mixed<Base, DataApi> {
abstract class Mixin extends base implements DataApi {
public setData( data: string ): void {
public setData( data: string | Record<string, string> ): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This goes in line with EditorConfig#initialData. Alternatively, MultiRootEditor may not extend DataApiMixin and implement these methods itself. I don't remember if that caused any problems (whether editor instance is used as DataApi).

But in this case, this is more safe, as we broaden the input parameter, and, as I said, data.set() already handles this type too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, note that getData() already supports multi-roots as you can pass the root name as an option.

So, to me it made sense to add setData() support straight here.

return;
}

export default function secureSourceElement( editor: Editor, sourceElement: HTMLElement & { ckeditorInstance?: Editor } ): void {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided to change this implementation because I don't see a reason why not to pass the sourceElement as a parameter. AFAIR, all secureSourceElement uses were straight after setting editor.sourceElement.

For multi-root, though, there's no editor.sourceElement -- there can be multiple. So, the new implementation fits the multi-root editor better.

* Read more about initializing the editor from source or as a build in
* {@link module:editor-multi-root/multirooteditor~MultiRootEditor.create `MultiRootEditor.create()`}.
*/
export default class MultiRootEditor extends DataApiMixin( Editor ) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided not to extend ElementApiMixin.

*/
public readonly editables: Record<string, InlineEditableUIView>;

public readonly editable: InlineEditableUIView;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This property doesn't make much sense for multi-root, where you have multiple editables, as above. This is set to the first defined editable.

I tried not extending EditorUIView but this got me into other typing problems (i.e. EditorUI requires EditorUIView, while Editor requires EditorUI, etc.).

Another idea would be to remove editable from EditorUIView and have it defined in particular EditorUIView implementations. This would require changes in places that uses editable. Alternatively, editable could be allowed to be null, then in places that requires it, we would have to tell TS that editable is set. AFAIR, editable is used in the balloon editor.

Of course, yet another idea would be to introduce hierarchy like I proposed for editor configs, so have separate ui view class for single and multi roots.

But I think that editable actually may not be needed at all. First of all, we have getEditableElement() on EditorUI that supports multiple editables (multi-roots). We could have getEditable() as well. On EditorUI or EditorUIView. We could also introduce getFocusedEditable() if there's a need to get the focused editable specifically (doesn't matter for single-editable editors, might matter for multi-root).

I do not think that we need to solve EditorConfig issues/changes; but this one is something that we might want to solve. editor.ui.editable does not make sense for multi-root, it smells. If you have proposals or strong opinion, I listen.

In general, I think there's quite a mess when it comes to which property is on what class and there are multiple ways to access some objects. E.g. we have sourceElement, we have editor.ui.view.editable.element we have getEditableElement() etc. I also don't understand why some things are on EditorUI and why other are on EditorUIView. However, this is not in a scope of this PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you checked how SourceEditing works with the current solution? I can see the button is active (but should not) but nothing happens when clicked. This looks like related to editor.ui.view.editable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is not supposed to make multi-root editor work with all the features, there will be follow-ups later.

I didn't check how source editing worked with earlier multi-roots but AFAICS in the code, it should work the same as earlier.

scofalik and others added 3 commits February 23, 2023 15:25
# Conflicts:
#	packages/ckeditor5-core/src/editor/editorconfig.ts
#	packages/ckeditor5-core/src/editor/utils/dataapimixin.ts
#	packages/ckeditor5-core/src/editor/utils/securesourceelement.ts
Copy link
Contributor

@godai78 godai78 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@godai78
Copy link
Contributor

godai78 commented Feb 24, 2023

Oi, master, of course, merge master. Duh.

Copy link
Contributor

@arkflpc arkflpc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please write ckeditor5-build-multi-root in TypeScript. See #13463 For inspiration.

@scofalik scofalik requested a review from arkflpc February 27, 2023 10:12
@scofalik
Copy link
Contributor Author

@arkflpc - done.

Copy link
Contributor

@niegowski niegowski left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review.

docs/updating/update-to-35.md Outdated Show resolved Hide resolved
packages/ckeditor5-core/src/editor/utils/dataapimixin.ts Outdated Show resolved Hide resolved
packages/ckeditor5-editor-balloon/src/ballooneditorui.ts Outdated Show resolved Hide resolved
packages/ckeditor5-editor-classic/src/classiceditorui.ts Outdated Show resolved Hide resolved
packages/ckeditor5-editor-multi-root/CONTRIBUTING.md Outdated Show resolved Hide resolved
packages/ckeditor5-editor-multi-root/package.json Outdated Show resolved Hide resolved
*/
public readonly editables: Record<string, InlineEditableUIView>;

public readonly editable: InlineEditableUIView;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you checked how SourceEditing works with the current solution? I can see the button is active (but should not) but nothing happens when clicked. This looks like related to editor.ui.view.editable.

@scofalik
Copy link
Contributor Author

@niegowski I fixed everything according to your suggestions, except where we had discussions.

@scofalik
Copy link
Contributor Author

scofalik commented Feb 28, 2023

I forgot to add a comment after pushing the changes.

These tests are removed because these editors do not support being created on a <textarea> element. The code is never fired except of a theoretical use case in the test. It brings confusion and makes the code messy and may give you false idea that something is possible.

@scofalik
Copy link
Contributor Author

@niegowski Fixed your suggestions + CI looks green.

@niegowski niegowski merged commit 50b9c4f into master Mar 1, 2023
@niegowski niegowski deleted the ck/11493 branch March 1, 2023 11:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce the multi-root editor build
5 participants