I am working on an inline formatting tool for the editorjs opensource component. The objective is to mark portions of text as input or output. The challenge in making such a tool is that we need to deal with the nested nature of html. For our objective it is clear that text cannot be input and output nor we can have output inside input.
We already have a base control that works, but it does not handle nesting properly. We need to come up with a process that can be implemented with the surround(range: Range) API call. And we need to describe the different cases that we need to support, considering the user experience. For example, the user right clicks and sees the tool menu with the specific tool highlighted or not. Then the user presses the button. At that point it is up to the code to decide what the behavior's should be.
Taking all of this in mind lets start with the use cases, followed by any useful API that is available and is relevant to out task. Then we can describe logically but as concise as possible what the code will do. It is important to distinguish between longer elaborations like this one. And short and concise reference descriptions. We will interleave between the two, and it may take a few interactions to get our reference descriptions correct.
Great, let's start by defining some use cases:
-
Single Word Selection: The user selects a single word, with no nested HTML elements.
-
Multiple Word Selection: The user selects multiple words, possibly spanning multiple HTML elements, but not nested.
-
Nested Selection: The user selects a section of text that includes nested HTML elements.
-
Full Element Selection: The user selects a full HTML element, possibly including nested elements.
-
Multiple Element Selection: The user selects multiple HTML elements, possibly including nested elements.
-
Overlap Selection: The user selects a section of text that overlaps with an existing input or output mark.
-
Unmarked Selection: The user selects a section of text with no existing input or output marks.
Next, let's consider some relevant APIs and methods from EditorJS:
-
surround(range: Range): Surrounds the currently selected range with a specified HTML tag.
-
checkSelection(): Checks the current selection and returns the Block where the selection ends.
-
getBlocks(): Returns all blocks present in the editor.
-
getBlockByIndex(index: number): Returns the Block at the specified index.
-
save(): Returns a Promise that resolves to the saved data.
Given these APIs, let's propose a rough process for the surround method:
-
First, the user's selected range is retrieved. This is done via the user's interaction with the EditorJS interface.
-
The
checkSelection()method is called to get the Block in which the selection ends. This can be used to confirm that the selected range is within a valid Block. -
The method will then check the current status of the selected text, which could be unmarked, marked as input, or marked as output. This will influence the behavior of the surround method.
-
If the selected range is unmarked, the method will proceed to surround the range with the chosen HTML tag (either input or output).
-
If the selected range is already marked, the method will need to handle the nesting issue. Here, it can follow a few different rules:
a. If the selected range is marked as the same type as the chosen tag (input or output), the method will simply remove the existing tag, effectively unmarking the text.
b. If the selected range is marked as the opposite type, the method will not proceed with the surround operation, to avoid nesting of input within output or vice versa.
-
After processing, the
save()method is called to save the changes.
In terms of UX:
-
When the user right-clicks and brings up the tool menu, the specific tool (input or output) can be highlighted based on the current status of the selected text. If the text is unmarked, neither tool is highlighted. If the text is marked as input, the input tool is highlighted, and the same goes for the output.
-
When the user presses the button to mark the text, the surround operation is performed as described above. The button press triggers this operation, but the behavior depends on the current status of the selected text and the chosen tool.
Finally, the reference descriptions:
-
Single Word Selection: The selected word is checked for existing tags and surrounded with the chosen tag, if appropriate.
-
Multiple Word Selection: Each word in the selection is checked and surrounded individually.
-
Nested Selection: The method must recursively check each nested element and apply the surround operation if appropriate.
-
Full Element Selection: The entire element is checked for existing tags and surrounded with the chosen tag, if appropriate.
-
Multiple Element Selection: Each element in the selection is checked and surrounded individually.
-
Overlap Selection: The method must discern