BobaEditor Codebase Guide
For installation instructions check out Setting Up Your DevEnv.
There are two important folders:
- src: where all the source code is.
- stories: where the "testing stories" (the one you see on the demo's left-hand sidebar) are defined.
The main editor component is
Editor.tsx. This is a React Class Component which wraps a QuillJS editor within itself. It takes care of displaying text both in edit mode (when the
editable props is true) as well as read-only mode (with
editable props = false). ****Documentation for every prop is available in the comments for the TypeScript interface.
Toolbar component, contains the options in edit mode (e.g. bold, italic, spoilers, headers and so on). This should not be confused with
Tooltip.tsx which, instead, contains the options that are only available on empty new lines (e.g. add image, embed video, search gif and so on). The display of the Tooltip is controlled by the
maybeShowEmptyLineTooltip method within
Editor.tsx, which checks whether the cursor is presently on an empty line.
TODO: add a quick overview of how the delta format works to help people get a rough understanding of it.
Custom Nodes & Embeds
Custom elements and formatting are implemented using Parchment, Quill's document model, and stored under
custom-nodes. Rather than reading the suboptimal Parchment documentation, the easiest way to understand how these work is to look at how other types are implemented.
How Custom Nodes Work
There's two types of custom nodes:
block. If you're familiar with inline vs block CSS elements, I understand these to be roughly equivalent.
The entry point for block nodes (e.g. embeds) is the "create" method. This is called by Quill every time an embed of the given type is added to the editor, either manually by the user or as part of loading a saved delta. In the first case the argument will be the value supplied by the user (e.g. the embed URL in case of embeds), in the latter the argument will be the value stored in the embed configuration, which you can see returned by the "value" method.
Embeds loaded through iFramely are handled by subclassing
OEmbedBase.ts with embed-specific logic.
OEmbedBase.ts ****also handles "best effort load" embeds.
Styling for embeds is handled by
CustomNodesStyle.tsx. Note the
:global() instruction wrapping class names, which will have to be cleaned up at some point (but is necessary for now).
How to Add a New Embed Type
- After adding a new embed in
custom-nodes, note the
blotNameexported by the static class created (e.g.
oembed-embed, etc). NOTE: we should probably standardize these at some point).
- Add your new embed type in
tooltipConfig.ts. The default configuration is used by the large "contributions" editor, while the single line configuration is used by comments.
How Custom Nodes are Loaded into Quill
All valid embed files within the
custom-nodes folder are automatically loaded into Quill . This is performed by the
addCustomEmbeds method in
Editor, which also automatically adds two embed lifecycle methods:
onLoadCallback: called when the embed has actually finished loading (content included).
onRemoveRequestCallback: called when the removal of an embed has been requested.
addCustomEmbeds also provides embeds with the
cache object (used to store embeds that have already loaded so we won't need to reload them over and over again), and with the
embedsFetcher, which is used to retrieve data from the backend.
Beside the Editor itself, BobaEditor's package exports include:
setTumblrEmbedFetcher: must be passed a method that, given a Tumblr url, returns the corresponding embed. This is because Tumblr's embed library returns a CORS error on regular Tumblr embed urls, making it basically useless.
setOEmbedFetcher: must be passed a method that given a url returns the response given by the iFramely embed service (or equivalent).
getAllImages: a method to get all the images within a Delta. Used to extract the images for upload.
replaceImages: a method to replace all the images within a Delta. Used to swap out images with the new, uploaded ones.
removeTrailingWhitespace: can be used to optionally remove any trailing whitespace from a delta.
// Print all the debug logs related to the editor
localStorage.debug = "bobapost:*";
// Print only the debug logs related to embeds
localStorage.debug = "bobapost:embeds:*";
// Print only the debug logs related to Twitter embeds
localStorage.debug = "bobapost:embeds:tweet";
// Print all the debug logs minus those ending with -verbose
localStorage.debug = "bobapost:*,-bobapost:*-verbose";
If you need to do this, please let me know and I will have you implement an easy way to do it for everyone to use. I know what should be done, but I don't have time right now.
You can click around in storybook to check whether everything is working as expected. Yes, this is less than ideal. BackstopJS is technically set up for this codebase, but it requires manual set up for every story in storybook and that's also terrible. If this is something you'd like to tackle, Implement Storybook Testing (Notion) is the task for you!