Building a Rich Text Editor: Day 1
Having built an editor twice, I realize there’s so much going on and I lose track of what I built a few minutes ago. Its the endless scrolling and ambiguous structuring throughout the project that made me lose focus. In this post, I’ll structure my project and identify key sub-problems before beginning with the real development. This is just to get keep me from getting lost in a large codebase.
A text editor is, in fact, much similar to building a robot. When I treat the editor as a robot, I would say that the robot has got a list of sensors and components(Keyboard, Mouse, and Screen) so he knows if any human is telling it to do something. So, there are some data streams(events like click, mouseup, keydown) that report the data to the robot. It would analyze data from all 3 devices and make a decision(event handlers) and perform something(reflect any edits on Screen).
If I put the behavior described above in technical terms of building a webpage, the core editor logic will have:
- Event Handler Section: receiving all data streams(keyboard and mouse) input through DOM.
- Custom Processors: interpreting the combinations and making a decision based on the current state of the document. This is meant for the high-level implementation of various document features.
- DOM Manipulators: after making a decision, any change made visually will be through a manipulator.
- Utilities: These are helper functions that will never directly interact with the keyboard, mouse, or screen. They are mainly a dump of some operations that are used frequently by the above 3 sections.
I’ll start with creating a project with
ng new text-editor and create the following structure:
| | fonts
| | images
| | styles
| | config
| | models
| | modules
| | | text-editor
| | | | components
| | | | | editor <- generated using angular cli
| | | | util
| | util
assets folder is pretty intuitive. I'll have all the resource files like SVGs, PNGs, and even global styles like color themes here.
app is the root of all source code:
config: The files here will only contain constant variables.
models: All logical classes related to the editor are supposed to be placed here. These models will never directly interact with any external entity like DOM, mouse or keyboard. They are meant to represent the current state of the document.
modules: This is will contain Angular Modules. We'll have 1 module to contain all document components. Each module will have a
components directory to hold all components and a
util directory to hold any directives or services.
util: We might need additional helper tools that are not necessarily tied to a Document's behavior, for instance, Rope Data Structure. If I had chosen to implement Rope, I'll put it here.
Developing Model Designs
Where to start?!?!?! I usually start by staring at the UI design and any Core Data Structures that have been already developed.
I’ll leave the editing tools in the header for now. I’ll start with developing the core model and try to link it to the DOM. I’ll be adding an
title attributes to the document. It’ll make it easy to store it in databases and help me locate a doc in DOM. Let's create Block.ts, Page.ts, and Doc.ts files under models.
Before I begin editing our angular component text-editor, I’ll organize the structure because I believe this file is going to be huge.
These sections are the ones described in the 4 points above. I’ve renamed Custom Processors to User Operations. All high-level implementation of features like FindAndReplace will be placed here. The other sections are placed exactly as described above.So, I’ll create a dummy document and build the HTML on the go.
I’m not sharing the
.scss file. It's going to be lengthy. w3schools.com was the one-stop-shop for me to learn to style.
I do have a page here. But, what about the 2 dummy blocks? I need to have some kind of getter setter tools to set data in a block in DOM. If you inspect the page element on this page, you’ll see 2 block divs with empty content.
In order to perfectly locate the div, I’ll give unique IDs to each page and block. Furthermore, we need some tools to place the content in DOM.
I’ve updated the divs for blocks and pages to support
(onCreate) event. Angular doesn’t provide for an event that gets triggered every time content renders.
So, say I change the block content using the doc object in
component.ts file, it won’t get reflected here. To handle this, I’ve created an on-create Angular Directive based on a StackOverflow answer. It was a little tricky to create this one though.
So, every time I update the page array or block array in doc object, changes get reflected here.
This is our base. It should be working fine now. I’m going to list down the sub-problems that I can think of and solve them iteratively in the following articles.
Convert Line Breaks to Blocks
Following up on the motive behind blocks, every time I press the enter key, it shouldn’t expand the current block. Instead, a new block needs to be created just after the current block. I’ll add a simple event keypress event handler and implement it:
Detect and Trigger Pagination on Content Overflow in a Page
The content is dynamic. I cannot count the number of characters and check with page dimensions.
One possible solution is to check the height of each block in DOM and compare it with page height. But, wait. Notice that the scrollbars automatically appear when content overflows in a div. There must be some mechanism through DOM APIs themselves.
The DOM already provides for outer height and internal scroll height values. So, comparing their values is an easy solution to check overflow.
Detect and Trigger Blockination on Content Overflow in a Block
If you keep on typing, you’ll see that a block expands vertically to make space for more characters. Now, this is a little tricky I think. I cannot use the solution for page overflow here because blocks will never be of the same height. They are of various heights owing to varying font sizes. Well, currently, I can’t think of a solution. F.
Text Selection across Multiple Blocks
The webpages do not allow us to select multiple divs in one go. If you start selecting text and move the mouse across multiple blocks, you won’t be able to select other blocks. There has to be a mechanism to detect mouse movements and highlight blocks manually. I’ll create a mouse object
models/MouseData.ts and refine it later:
Wrapping it off for now.