Testing Out the Lexical Implementation
The importDOM/exportDOM behavior of Lexical can sometimes cause Content Layout Shifts. I am using this post to test out all components to try to minimize content layout shifts.
About Lexical State Handling
The way this site handles Lexical Editor States - when communicating with the server - is by:
- exporting the DOM to JSON on the client using
editor.getEditorState().toJSON()
- calling
exportDOM
on the server - Saving both the HTML and the result of
exportDOM
in the database - exportDOM is called on the server right now using JSDOM.
- Saving both the HTML and the result of
- rendering the result of
exportDOM
when the lexical state needs to be rendered - Calling
importDOM
on the client when a lexical state needs to be rendered
importJSON
is used when copying and pasting things around the editor. As a result, importJSON
, exportJSON
, importDOM
, and exportDOM
all need to be optimized so that they accurately represent an editor node, and exportDOM
needs to be optimized to prevent Content Layout Shift from occurring.
Note: importJSON, exportJSON, importDOM, and exportDOM are all methods that each node in the Lexical editor has.
What is This Page
I am using this page to test out the methods described above for each Lexical node to make sure that they all work correctly.
Lexical Nodes
This list does not include text nodes and nodes that are used only as a part of a larger node, e.g. TableCellNode
and
Heading Node
The heading node is used to render heading elements <h1>
- <h6>.
There should be a keyboard shortcut that allows users to toggle between heading elements. See below:
Element | H1 | H2 | H3 | H4 | H5 | H6 | P |
---|---|---|---|---|---|---|---|
Keyboard Shortcut | ALT+H+1 | ALT+H+2 | ALT+H+3 | ALT+H+4 | ALT+H+5 | ALT+H+6 | ALT+P |
The heading node inherits properties from the extended ElementNode - so it can have a block-level background color, borders, custom margin / padding, box shadow, and outlines.
Quote Node
The quote node allows for the rendering of <blockquote>
elements - for when the user wants to insert a quote. There is really nothing special about the way this node is handled.
Self control means wanting to be effective at some random point in the infinite radiations of my spiritual existence.
Franz Kafka
List Node
The list node allows users to insert ordered and unordered lists. The list node inherits properties from the extended ElementNode - see the description of this node above. The max depth of a list node is 5. You should probably listen for text changes - and if the current selection is collapsed and contains the text -
or \d+.
, then you should insert an unordered list or ordered list, respectively - this allows ordered lists to start at a place other than 1.
- This
- is
- An
- Ordered
- List
- The list
starts
at 123 using the method described above.
- This
- is
- an
- unordered
- list
Link Node
The link node allows users to insert links - anchor tags (<a>
). The Link node has url
, target,
rel
, and title
attributes. The url attribute is the webpage the link is linking to, the target should always be _blank, and the rel
attribute should be ugc because the link is user generated content - we don't know what the user is linking to.
Aside Node
The aside node allows users to insert <aside>
elements. An example of an AsideNode
can be seen to the right. These nodes can either be inserted to the left or right side of a ParagraphNode
only. There is a side
attribute that the AsideNode
uses to keep track of which side the Aside node should appear on. The reason the AsideNode
was included in the Lexical editor is to give people the opportunity to explain things that might break the flow of the article - you can see examples of <aside>
elements around the web if you read news / blog articles.
Details Node
The details node allows users to insert <details>
and <summary>
elements.
Here is an example of a <details>
element. Here is the mdn description of the <details>
element. Similar to the <aside>
element, the details element should probably be used to give additional information to the user when you don't want to interrupt the normal flow of the article.
Table Node
The table node allows users to insert tables with a maximum size of 50 columns x 50 rows. The user can customize the table by adding banded rows / banded columns, customizing the background color, borders, and vertical align of cells, and customizing the width of columns / height of rows.
YouTube Node
The YouTube node allows users to embed YouTube videos in an <iframe>
. The alignment of a YouTube video - left, center, or justified - can be customized.
News Node
The News node allows users to link to news articles on other sites similar to other platforms. The requirements for a news article to be embedded are specified in the Embed News Content dialog.
Horizontal Rule Node
The Horizontal Rule node allows users to insert a horizontal rule (<hr />
element). The width and color of the horizontal rule is customizable. The default horizontal rule can be seen above.
Audio Node
The audio node allows users to insert audio files - <audio>
elements. This let's users upload audio without having to upload a video.
Video Node
The Video node allows users to insert video files - <video>
elements.
Image Node
The image node allows users to insert images - <img>
elements. When uploaded, the image initially has an unassigned width and height - the width and height can be changed by resizing the image. The image can be aligned left, center, right, or justify.
Image Carousel Node
The image carousel node allows users to render image carousels using splidejs.
Code Node
The code node allows users to insert <code> elements that are highlighted using a variety of coding languages.
#include <stdio.h>
int main() {
printf("Hello World!");
}
Paragraph Node
The paragraph node allows users to insert paragraphs - these are the most common nodes.
Math Node
The math node allows users to insert block level and inline math content using MathML and KaTeX. Here is some block level Math content:
Here is some inline Math Content, the Pythagorean Theorem: . You need to double click on inline math equations to edit them.
Testing out the MathNode
for Longer Equations:
- Needed to change the
MathNode
to haveoverflow-x: auto
Text Node
The text node allows users to insert bold content with the <strong> tag, italic content with the <em> tag, strikethrough content with the <span> tag, underline content with the <span> tag, some superscript content with the <sup> tag, some subscript content with the <sub> tag, inline code content with the <span>
tag, and inline quote content with the <q> tag
.
Known Problems
- The
ElementNodeWithBlockStyle
node is not exporting / importing borders correctly. The border radius is not working correctly.
There maybe a problem of appropriately escaping lists that needs to be looked into.
Details Element
: There may be a problem with deleting the details element.- When an empty paragraph that is immediately following a
<details>
element is deleted, the entire details element is deleted. Instead, the behavior should be that whatever is inside theAccordionContentNode
should be focused. - Process when deleting Paragraph:
- KEY_BACKSPACE_COMMAND: LexicalRichText
- DELETE_CHARACTER_COMMAND: LexicalRightText
- selection.deleteCharacter()
- ParagraphNode.collapseAtStart()
- If prevNode is DetialsNode, set DetailsNode open and select the AccordionContentNode
- When an empty paragraph that is immediately following a
TableNode
:- When the Edit Selected Cells dialog is closed, the page does not scroll back into the correct position.
- When the Edit Table dialog is closed, the page does not scroll back into the correct position.
- The Edit Table dialog does not work correctly.
- When another lexical editor on the page is focused, the
activeCell
for theTableNode
s in the previously focused editor should be set tofalse
. - The
height
property forTableRowNodes
is not working properly - this is working fine now. - On load, there is a problem with reading the
activeCell
of aTableNode
(See image below)
-
TableNode
should be changed so that when editable is set to false, there is aTableWrapper
node that is inserted and theTableNode
is then inserted into the table wrapper - The table wrapper should provide horizontal rule
NewsNode
:
- Improve the styling of the
NewsNode
- I think it looks pretty good now - search for
.
lexical-news-article
when you need to change it again
- Improve the styling of the
VideoNode / Audio Node
:- For the video node / audio node title input - need to remove some inline styling
- Need to improve the styling for the video - title should have an
.h6
class and the caption for the video should have a.body1
class. - Might want to transcode video files differently - look into adaptive bitrate streaming.
- Need to change to Adaptive bitrate streaming
- Might want to also decrease the max-height of video nodes to something less than 400/350 (idk what it is now) - it seems to be less on other platforms.
- You also have to think about vertical video here - which might be the most typical type of video (iPhone self recording)
CodeNode
:- When starting a new line, it is impossible to add a tab or space
- Look into INSERT_TAB_COMMAND handlers
- This was solved by fixing the
white-space
attribute of thecode.lexical-code
styling
- Need to keep track of when the
CustomEvent
NEW_CONTENT_LOADED
is called in Lexical. It shouldn't be called when rendering lexical - only as the result of commands being dispatched (when the editor is being created). - Something is wrong with image upload (and maybe audio and video upload as well)
- First of all, for image, audio, and video upload, we want to prevent showing the filename of the uploaded media file - this needs to be addressed
- Secondly, the image upload doesn't allow you to update additional images after you have already uploaded one - this needs to be addressed
- There is a problem with disabling / un-disabling buttons on the toolbars that needs to be addresses (the ones that I have noticed are embedding YouTube/News and Quotes)
Things to Address
LinkNode
: We don't know whether the user is linking to an unsafe website.- There maybe should be a listener that listens for when these links are clicked. The listener launches a dialog that makes the user confirm they want to go to whatever site is linked.
- You could use the Google Safe Browsing API to determine whether the site is safe, and if it is not, change the
LinkNode
to aTextNode
on the server. - Some combination of the two, e.g. Only show the dialog when Google says the site is unsafe / doesn't know whether the site is safe, and tell the user that we don't know about the site.
LinkNode
: Inserting a link using the floating menu - the popover for the link should be dispatched relative to the floating menu, not in its normal position.- The problem with this is we don't know how to address the appropriate link node popup with the
data-pelem
attribute, unless we add a
- The problem with this is we don't know how to address the appropriate link node popup with the
- Spacing between sections of an article: there should be some
margin-top
on all article sections except for the first - otherwise, it will be difficult for users to efficiently. space out their content. - When a word in a text node receives the
dblclick
event, the space to the right of the word is also highlighted, which makes it difficult to effectively insert inline-code blocks and sometimes links. - Apparently this is a browser behavior that probably doesn't need to be changed
- You should probably calculate how certain Lexical Sections and Code Sections and Markdown Sections will affect page load and implement pagination (or at least lazy loading of sections of an article).
Improvements to Make
- When a
ParagraphNode
is inserted inside anAsideNode
theParagraphNode
should probably have its margin set to 0 immediately with thesetBlockStyle
method - this looks better. - At the begging of a
ElementNodeWithBlockStyle
, maybe allow the user to specify CSS within brackets or something. - If the list / heading node / paragraph starts with an expression matching this regex:
/^css[]$/
, then convert the CSS and style the node appropriately. This will prevent the user from having to open up the dialog.
- If the list / heading node / paragraph starts with an expression matching this regex:
- When you add Keyboard shortcuts to the Alt key, there should be a timeout where the default is prevented to prevent the default
Alt
behavior. - Implement an inline node with the
TextNode
for the<kbd>
keyboard input element. - Server:
- All
fetch
calls oraxios.get
/axios.post
calls on the backend should have a timeout.
- All
Keeping Track of Certain Decisions
- Why not Include an Image Grid for Uploading Multiple Images?
- For one, I don't want to look into how to do it properly.
- Secondly, The users have the opportunity to upload custom code - where they can create image grids themselves
- There are enough tools out there today that should allow users to stick together images into a grid
- I think the Carousel should be enough
Notes on Editable Change
- Some things are hard to manage in both editable / not -editable lexical states.
- When the editor changes editability, we:
- set the
data-no-edit
on the root element of the editor - Remove the character counter if there is one on the editor
- We remove the
tabindex
attribute from the row and column resizers
- set the
- We should:
- Wrap the
TableNode
in aTableNodeWrapper
element - You need to update the TableNodes that are currently in the database
- You need to check whether any
TableNode
s don't have aTableWrapperNode
as a parent when validating editor
- Look at MathNode and TableCellNode for listeners that are added
- Wrap the
How To Update Lexical States
Reasons You Might Want to Update the Stored Lexical State:
- You updated the
exportDOM
behavior of a node and you want the new version of exportDOM to be stored in the database so that the lexical state renders correctly. - You changed the properties that a Lexical node has, and you want to add a property to the nodes in the database.
- Make sure to test function and/or create a Database Snapshot before doing this
- Check out the lexical/index file on the backend for an example of this
- I just did this to implement
TableWrapperNode
- I just did this to implement
Since you have the JSON and the HTML for the lexical state stored on the server, if you want to update the lexical state for a node / type of node, you can query the database for the nodes and call the updated exportDOM on the JSON to solve the issue described in 1. or you could add a default property / derive an attribute from the node to solve the issue described in 2..
Lexical does have a versioning system to help you avoid breaking changes. The version of a node should be stored as a version
attribute when calling exportJSON and a data-v
attribute when calling exportDOM. Breaking changes should only occur when the way a node is rendered completely changes - you should try to prevent this.
Make sure, when you add a new node to lexical, to register the node on the backend implementation
Comments
There are currently no comments to show for this article.