Lab 3: Editable Nodes
Released: October 12th, 2023
Due: October 18th, 2023 at 11:59 PM ET
To get checked off for this lab, meet with a TA at your lab section or TA hours
⚠️ Do not clone the Assignment 3 stencil code until you have submitted Assignment 2 for the last time to Gradescope. We will be checking to make sure you do not submit Assignment 2 after cloning Assignment 3. ⚠️
Congratulations ~ you have successfully created and deployed your very own hypermedia system! You can create anchors and links between nodes, within the same node, and follow links, but there is so much more that you could be able to do in a hypermedia system!
In this unit, you will be turning the system from a static, view-only hypermedia system to one that is interactive and usable.
In this lab we are laying out the foundation as we get ready to implement editing the properties of the INode
s that we have created. As always, if you have any questions about anything, please reach out to a TA or message us on Slack!
Objective: Introduce you to NPM
packages, Prosemirror
, Tiptap
, and how to make your content editable!
tiptap
Great job on a tough assignment! Please let us know what went well and what didn't by completing this anonymous feedback form for Assignment 2.
We need to link all of your lab checkoffs, hypothesis annotations, and assignment submissions in order to calculate midpoint grade reports. Please fill out this form so we can get all the necessary information.
For the final project, you will be working in groups of 3 to build your very own hypermedia system
! As you are already aware, the scope of a hypermedia system is extremely broad – it can range from a system that focuses on temporal media
to one that focuses on visualisations
, or even just a system like Wikipedia
that is primarily textual.
We want to get you thinking about what type of hypermedia system you want to work on for your final project early, because in Unit 4: Additional Hypertext Features you will need to begin making choices relevant to what hypertext system you want to build.
Task 1: Write out a list of areas that you would be interested in exploring. This can range from as broad as just text nodes
to a particular technology you would like to implement like a motion tracker
. This task is merely to get you thinking so don't worry if you can't come up with much!
Feel free to discuss this with a partner!
Note: The demo may take up to 30 seconds to load due to the free backend we used for hosting. Additionally, you might notice some strange behavior if there are multiple students interacting with the demo at the same time.
You can find a demo of the MyHypermedia application here.
You can clone the repository here.
The same repo will be used for Lab 3 and Assignment 3!
Always remember to run npm install
in both the frontend and backend folder as well as adding your .env
file with the appropriate environment variables to the server
folder. Let us know if you have any questions!
npm
modules from the npm
registryAs a npm
user, you can create and publish public packages that anyone can download and use in their own projects. As a developer, writing your own code for everything can become tedious and challenging, and therefore the npm registry
provides free open source code that you can use for your projects.
It is vital that you have an understanding of how to install new npm
modules so we will be walking you through an example of how to install one and also giving import tips along the way to explain how you can ensure that you have a well-managed codebase.
Web applications usually use a great number of third-party packages and libraries so we don’t have to write everything on our own. Instead of manually installing the packages one-by-one, we use the package manager to automate this process. It’s really quick!
Project dependencies are the packages that are being used in the code of the project. We have separated our frontend and backend into two seperate folders, each with their own project dependencies. There are multiple types of project dependencies, which are outlined below.
Task 2: Look at the package.json
file in the unit3-editable-nodes/client
folder. Notice how there are seperate lists fordependencies
and devDependencies
? Each dependency has the name in the npm
registry and the version that your current project is compatible with.
These are the dependencies that our web application needs to run. For example since our web application is working with React we need a react
node package in our application. These dependencies are specified under the key “dependencies” in package.json
file.
These are those dependencies which are needed at the time of development but are not responsible for working of the application i.e. even if we skip these dependency our application will work just fine. An example of a dependency that would go into this list is ESLint
, which checks the Java/TypeScript code we write for common problems, such as syntax errors, formatting issues, code style violations, and potential bugs.
Our project has no optional dependencies, but as the name suggests, these dependencies are optional. If they fail to install, npm
will still say the install process was successful. This is useful for dependencies that won’t necessarily work on every machine and you have a fallback plan in case they are not installed.
Peer dependencies are primarily used when you are developing a plugin/package for a host tool or package. That means you expect the user of the plugin/package to have these dependencies installed while not necessarily using these dependencies in your plugin/package.
You won't have to worry about including peer dependencies for this web application, but you may come across the message: "some_dependency requires a peer of <another_dependency> but none is installed. You must install peer dependencies yourself"
.
If you see an error like this, then you should install <another_dependency>
yourself!
Your backend setup for the hypertext system we are building is mostly complete! Thus, this lab/assignment will mostly be devoted to frontend development inside the client
folder. In terms of scope for your own projects, particularly the final project, it is likely that you will want to alter the backend types
to change what the metadata of your node objects look like.
For example, if you wanted your application to handle users, you would want to create an additional MongoDB
collection (user-model
) to store the users.
Task 3: Time to get the backend running! You should be an expert on this by now! Use the same .env
file that you did for your own MongoDB connection in Unit 2 and add it into your server
folder.
It should look something like this:
You may need to change the port number. If you do, make sure the port number in the server .env
file matches the one in the client endpoint.ts
.
Now that you have a better idea of what dependencies
are and how they work, you are going to install your own package. Remember all of the tedious code that we were writing during the last lab? This package handles innerHTML
manipulations for us! This package is extemely useful for editable text
and we will be using it for Unit 3
.
ProseMirror
?ProseMirror is a toolkit for building rich text editors - it is not an out-the-box solution which some packages may provide (e.g. https://github.com/bkniffler/draft-wysiwyg). This means ProseMirror has a steep learning curve - there are many concepts and terms to learn, and it can be difficult to structure your codebase in a logical manner.
tiptap
?Tiptap is a wrapper library for ProseMirror, it is an abstraction layer that makes ProseMirror easier to work with, and provides React and ProseMirror integration.
Tiptap provides extensions that abstract over various ProseMirror concepts such as schemas, commands and plugins, making it much simpler to group related logic together.
In the assignment itself you will be using tiptap
to make your text editable. In this lab, text nodes will not support linking and anchoring; setting those up will be explained and included in the assignment.
Task 4: Make sure to cd
into your client
folder in your terminal, then run the following command to install tiptap
(documentation here)
This will add tiptap
to your package.json
dependencies and also install other relevant packages. In particular, when you are installing tiptap
, you are also installing the relevant ProseMirror
packages.
Wondering where all of the code goes? It goes into your node_modules
folder! If you can find the @tiptap
directory, that means that your installation was successful!
tiptap
in your codebaseOnce you have installed it, it's ready to use - getting all of that useful code is really as easy as that!
When you have packages installed, you can access them in any of your files. Since we use ES6
(read more here), the syntax for importing packages is:
import Donut from 'PVDonuts'
In earlier version of JavaScript, require
was often used to import packages, however since ES6, and throughout this codebase we will only be using import
. So to import tiptap
into our codebase (in particular TextContent.tsx
, because that is where we want to use it) - we add the following lines:
Task 5: Go to client / src / components / NodeView / NodeContent / TextContent / TextContent.tsx
and add the aforementioned tiptap
imports to the top of the file
Note: At this point, you may still see some red warning messages in your code. That is fine.
tiptap
editorTask 6: Creating the tiptap
editor!
Do this by creating an instance of the editor by replacing const editor = null
with
Once you have added the editor, you will want to add the tiptap
components that we are actually going to render. You should replace the final return statement (not the one in the !editor
conditional), with the following:
We've created a basic, editable text component using tiptap
, without any buttons to apply styling.
Confirm that you're able to add and delete text from a text node as expected.
Note that we haven't done anything to save our changes to the database, so all of your changes should go away when you leave the node. We'll now update the database so your changes persist in Assignment 3!
Task 7: Follow the instructions below to add a button that can make text bold
The way that tiptap
works is that it has a set of commands that you can use to alter the text in the editor. Rather than you having to go in and change the inner CSS, tiptap
handles everything for you!
There are some really cool tiptap
extensions - ranging from simple marks like Italic and Bold text, to getting setup with code snippets
. For this lab, we'll walk you through setting up the bold extension.
The bold extension is already included in StarterKit, so we don't need to add it separately to our tiptap editor
extension list. By default, we can make the text bold using the ctrl / cmmd + B
keyboard shortcut, but we also want to add a button to visually show users that they can make text bold.
To add a button to make text bold, add the following into the return statement of the TextMenu.tsx
file:
You should end up with editable text which looks like the following image:
Tip: When we implement things it's important to make sure everything is functioning as expected before improving the user interface. In Assignment 3 you'll improve the user interface!
Task 8: Add the ability to make the text italicized! This should be a matter of doing the exact same thing we did for bold, except with toggleItalic
, instead of toggleBold
. You should reference the tiptap
documentation as needed.
Like with bold, the italic extension is already included in StarterKit, so we don't need to add it separately to our tiptap editor
extension list. If we wanted to add a different extension that isn't in StarterKit, we would need to list it in the list of extensions in useEditor
.
client / src / components / NodeView / NodeHeader / NodeHeader.tsx
where you will find the TODOs for this task!title
and whether it is in an editing
state or not, they are as follows:Task 9: We will now fill in handleUpdateTitle
, which is a function that updates the text in the database
This method takes in the updated text and adds it to the database using the NodeGateway.updateNode
method. Get used to using that method, because we'll be using it a lot!
It is important that we also let the user know if the update failed! This is an important thing to do in any web application because otherwise the user is misled to believe that the update successfully was written to the database when in reality it has not!
Put the following code inside the handleUpdateTitle
function located in NodeHeader.tsx
. Right now, this just sets up the logic for handling title changes, which we will be able to use once task 10 is complete.
One thing to consider, and a notable design decision, is how frequently the EditableText
makes a call to the database. Currently whenever any letter in title
changes it updates the database by making a call to update for every onChange
. This could also be done by only making a call to the database onBlur
. Have a look at EditableText
to see how exactly it works!
The onBlur event occurs when an object loses focus.
The onChange event occurs when the value of an element has been changed.
Task 10: We will now add three different ways to make the text editable! Note that it is not always ideal to have all three of these implementations as there may be conflicting or inaccessible behavior. However, for the purpose of this assignment, this gives us the oppurtunity to introduce different ways to update the title.
Essentially what we are doing here is looking at different ways where editingTitle
is true so that the title is in editable
mode.
DoubleClick
The first thing that we should do is add the onDoubleClick
tag to the nodeHeader-title
text <div>
, so that when the user right-clicks
, it sets editingTitle
to true.
ContextMenu
When it comes to context menus, we do not always want to use our own context menu because the default context menu (see attached image) has some important components when it comes to accessibility. For example, someone may need to be able to Translate
a selected portion of text. Using a custom context menu would prevent them from doing so.
The first thing that we should do is add the onContextMenu
tag to the nodeHeader-title
text <div>
, so that when the user right-clicks
it calls the method that we pass in. We would do that as follows:
In the handleTitleRightClick
method, what we want to do is make a call to the ContextMenuItems
list of JSX.Elements (which is an exported list from ContextMenu.tsx
). Essentially what we are doing here is saying that we should add an item to the list. You could add other context menu items if you like this way of interacting with content for your web app!
Note: There is a known bug in our usage of the context menu, where the context menu seemingly does not close after clicking away. This is okay/expected behavior. We will let you know if we make a fix for this.
Insert the following code in the handleTitleRightClick
function in NodeHeader.tsx
Tip: The following code snippet lets us determine what operating system the person is using. This could be used to help us determine what we should give as the itemShortcut
. On win
it would be ctrl
but on mac
it would be cmd
. This can be useful for other reasons as well!
Keyboard shortcuts
Keyboard shortcuts are pretty snazzy, but they also have shortcomings - for example, what we do in the code snippet below is say that if Ctrl + Shift + R
is pressed we should enter the editing title workflow - but the problem with that is we now lose the ability to reload the page (which Ctrl + Shift + R
also handles)!
The first step is to ensure that the browser is listening for keydown
events in the useEffect
on load. This uses addEventListener
, which you can set it up to listen for pointerdown
, pointerup
, and many more events!
The EventListener interface represents an object that can handle an event dispatched by an object in the HTML DOM. You can read more about it, and other EventListeners here.
Once you have added the line above, you should add the following switch statement into your nodeKeyHandlers
function. It is important to call e.preventDefault()
to prevent the possibility of the default shortcut (reload
in this case) also being called in addition to our custom shortcut.
tiptap
text editor with buttons to make the text bold and italic.