--- title: Lab 3 | Editable Nodes tags: lab --- <span style="font-size: 50px;">**Lab 3: Editable Nodes**</span> :::info **Released: October 13th** **Due: October 19th 11:59pm ET** To get checked off for this lab, meet with a TA at your lab section or TA hours ::: :::danger ⚠️ **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. ⚠️ ::: <!-- INFO BOXES :::success GREEN This is for any step by step instructions that students should follow. ::: :::info BLUE This is for important assignment information throughout the assignment: **Released: September 8th, 6:00pm ET** **Due: September 15th, 11:59pm ET** ::: :::warning YELLOW Use this info box for disclaimers. ::: :::danger Use this info box for important points that students should not miss! ::: :::spoiler Dropdown list Use this ::: --> <!-- TYPESCRIPT CODE BLOCKS ```typescript const list = [10, 20]; console.log(list.map(x => (x * x))) ``` --> <!-- HOW TO CHANGE COLOR IN MARKDOWN <span style="background:aliceblue">some text with a **lightblue** background</span> <span style="color:red">some **red** text</span> --> <!-- These are a list of shortcuts available. --> *[HTML]: Hyper Text Markup Language *[W3C]: World Wide Web Consortium *[NPM]: Node Package Manager *[IDE]: Integrated Development Environment *[MERN]: MongoDB, Express, React, NodeJS *[Yarn]: Yet Another Resource Negotiator # **Introduction** Congratulations ~ you have successfully created and deployed your very own hypermedia system! You can create anchors and links between nodes, or within the same node, and you can follow links, but there is a lot more that you should 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 foundations 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! :::success **Objective:** Introduce you to NPM packages, `Prosemirror`, `Tiptap`, and how to make your content editable! ::: ## **Checklist** - [ ] Complete Assignment 2 feedback form and student information form - [ ] Brainstorm ideas for your final project - [ ] Clone the repo for the lab - [ ] Get setup in your code base with `tiptap`. - [ ] Different ways of editing a node's title - Double click - Context menu - Keyboard shortcuts # Reflecting on Assignment 2 Great job on a tough assignment! Please let us know what went well and what didn't by completing this anonymous [feedback form](https://docs.google.com/forms/d/e/1FAIpQLSdQh5gJvFWkxlFkWZ1FKHiFlzEhDJ5AAaCPQkeEWNfMilgbNQ/viewform?usp=sf_link) for Assignment 2. # Student Information Form We need to link all of your lab checkoffs and assignment submissions to you in order to complete midpoint grade reports. Please fill out [this form](https://docs.google.com/forms/d/e/1FAIpQLSdbYX3f6DGre5C1mEVWDJRmZFo5NCF5xTDBSd_tMYJXNLimnw/viewform) so we can get all the necessary information to do so. # Beginning to brainstorm for your final project <div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:200px" src="https://c.tenor.com/OrKCLkgfSQMAAAAM/kstr-kochstrasse.gif" /></div> You will be working in groups of 3 to create your final project, 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 focussed on `temporal media` to a system that focusses 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. :::success **Task 0:** 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! ::: # Cloning the GitHub Repo You can clone the repository [here](https://classroom.github.com/a/VgdrJflW). :::success The same repo will be used for Lab 3 and Assignment 3! ::: Always remember to run `yarn install` in both the frontend and backend portions of the code as well as add your `.env` files. Let us know if you have any questions! # Using npm modules from the npm registry <div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:700px" src="https://snyk.io/wp-content/uploads/Malicious-code-found-in-npm-package-event-stream-downloaded-8-million-times-in-the-past-2.5-months-.jpg" /></div> As an 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 your 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! ## Yarn vs NPM In projects so far we have used `Yarn` instead of `npm`, although you could do everything with `npm` as well. **Wondering what the difference is between Yarn and npm?** They are both package managers to manage a project's dependencies. Facebook developed `Yarn` to fix performance and security concerns with `npm`. Rather than thinking of `Yarn` as a replacement for `npm` think of it something that acts as a superset of `npm` and provides a faster and more secure development environment. ## Dependencies 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 outlined below. :::success **Task 1:** Have a look at the existing `package.json` file in the `unit3-editable-nodes/client` folder. Notice how there are seperate lists for`dependencies` and `devDependencies`. Each dependency has the name in the npm registry and the version that your current project is compatible with. ::: ### Production Dependencies 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. ### Development Dependencies 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. At example of a dependency that would go into this list is `ESLint` ### Optional Dependencies Our project has no optional dependencies, but as the name suggests an optional dependency is optional. If they fail to install, Yarn 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 Peer dependcies 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 in your plugin/package. You won't have to worry about including peer dependencies for this web application but you may come across `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! # Backend ## Environment Variables Your backend setup is mostly complete! As a result, this unit will mostly be devoted to `frontend` and will be in the `client` folder with development. 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 to add a `user-model` you would also want to store the users as an additional `MongoDB` collection. :::success **Task 2:** You should know how to do 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. ::: ```bash= DB_URI = <YOUR OWN URI> PORT=5001 TSC_COMPILE_ON_ERROR=true ESLINT_NO_DEV_ERRORS=true ``` You may need to change the port number; if you do, make sure the port number in your server .env file and your endpoint.ts file match. # Frontend ## Environment Variables Add the following to your frontend's `.env` file (`client/src/.env`). ```bash= TSC_COMPILE_ON_ERROR=true ESLINT_NO_DEV_ERRORS=true ``` ## Installing a package for editable text 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 throughout `Unit 3`. ### What is `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 you codebase in a logical manner. ### What is `tiptap`? [Tiptap](https://tiptap.dev) 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. :::success **Task 3:** From the `client` folder, run the following command from the terminal to install `tiptap` (documentation [here](https://tiptap.dev/installation/react)) ``` yarn add @tiptap/react @tiptap/starter-kit @tiptap/extension-link @tiptap/core ``` ::: 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. :::info The above `Yarn` installation is the same as installing the `tiptap/react`, `tiptap/starter-kit`, `tiptap/extension-link`, and `@tiptap/core` packages seperately. If you were using `npm` then you would run: ``` npm install @tiptap/react @tiptap/starter-kit @tiptap/extension-link @tiptap/core ``` ::: 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! ### Using `tiptap` in your codebase Once you have installed it, it's ready to use - getting all of that useful code is really as easy as that! :::success **Task 4:** Go to `client / src / components / NodeView / NodeContent / TextContent / TextContent.tsx` and add `tiptap` imports ::: When you have packages installed, you can access them in any of your files. Since we use `ES6` (read more [here](https://www.w3schools.com/js/js_es6.asp)), 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: ``` import { Link } from '@tiptap/extension-link' import { Editor, EditorContent, useEditor } from '@tiptap/react' import StarterKit from '@tiptap/starter-kit' ``` That will import the necessary resources for us to get started! :::info Tip: `shift + alt + o` is a super useful shortcut in `VSCode` that organizes imports. Try it out in a file where you have a bunch of unused imports! ::: #### Creating a `tiptap` editor :::success **Task 5:** Create a `tiptap` editor! ::: Do this by creating an instance of the editor by replacing `const editor = null` with ```typescript= const editor = useEditor({ extensions: [ StarterKit, Link.configure({ openOnClick: true, autolink: false, linkOnPaste: false }), ], content: currentNode.content, }) ``` 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: ```typescript= return ( <div> <TextMenu editor={editor} /> <EditorContent editor={editor} onPointerUp={onPointerUp} /> </div> ) ``` :::danger **Warning**: If your screen goes white when navigating to a text node and you see the browser console error message `Uncaught RangeError: Adding different instances of a keyed plugin`, delete the yarn.lock file and node_modules folder in client, and re-run yarn install from the terminal. ::: 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 update the database so your changes persist in Assignment 3! **Task 6**. :::success **Task 6:** Adding a button so that you 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](https://tiptap.dev/api/marks) - 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: ```typescript= <button onClick={() => editor.chain().focus().toggleBold().run()} disabled={!editor.can().chain().focus().toggleBold().run()} className={ editor.isActive('bold') ? 'active-textEditorButton' : 'textEditorButton' } > Bold </button> ``` You should end up with editable text which looks like the following image: ![text editor with bold button](https://i.imgur.com/MyulIDy.png) :::info We know it's not the most beautiful layout - but 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! ::: :::success **Task 7**: 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](https://tiptap.dev/api/marks/italic) 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`. ## Editing the node's title 1. Go to `client / src / components / NodeView / NodeHeader / NodeHeader.tsx` where you will find the TODOs for this task! 2. We have two state variables to keep track of the `title` and whether it is in an `editing` state or not, they are as follows: ```typescript= // State variable for current node title const [title, setTitle] = useState(currentNode.title) // State variable for whether the title is being edited const [editingTitle, setEditingTitle] = useState<boolean>(false) ``` :::success **Task 8:** Write a method that updates the text in the database ::: 3. 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 mislead to believe that it has been uploaded in the database when in reality it has not! ```typescript= setTitle(title) const nodeProperty: INodeProperty = makeINodeProperty('title', title) const titleUpdateResp = await FrontendNodeGateway.updateNode(currentNode.nodeId, [ nodeProperty, ]) if (!titleUpdateResp.success) { setAlertIsOpen(true) setAlertTitle('Title update failed') setAlertMessage(titleUpdateResp.message) } setRefresh(!refresh) setRefreshLinkList(!refreshLinkList) ``` 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 databse `onBlur`. Have a look at `EditableText` to see how exactly it works! :::info The **onBlur** event occurs when an object loses focus. The **onChange** event occurs when the value of an element has been changed. ::: :::success **Task 9:** Add the different ways to make the text editable! Note that it is not always ideal to have all three of these as there may be conflicting, or inaccessible, however this also gives us an oppurtunity to introduce you to exactly how to set these three things up. ::: Essentially what we are doing here is looking at different ways we can make it such that `editingTitle` is true so that the title is in `editable` mode. 4. `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. In Unit 2, we introduced `e.detail` as part of the `onClick` method, this is another way of doing the same thing! ```typescript= onDoubleClick={(e) => setEditingTitle(true)} ``` 5. `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. <div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:300px" src="https://i.imgur.com/vouIV1g.png" /></div> The first thing that we should do is add the `onContextMenu` tag to the `nodeHeader-title` text, so that when the user `right-clicks` it calls the method that we pass in. We would do that as follows: ```typescript= onContextMenu={handleTitleRightClick} ``` 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. ```typescript= ContextMenuItems.splice(0, ContextMenuItems.length) const menuItem: JSX.Element = ( <div key={'titleRename'} className="contextMenuItem" onClick={(e) => { ContextMenuItems.splice(0, ContextMenuItems.length) setEditingTitle(true) }} > <div className="itemTitle">Rename</div> <div className="itemShortcut">ctrl + shift + R</div> </div> ) ContextMenuItems.push(menuItem) ``` :::info 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 `cmmd`. This can be useful for other reasons as well! ```typescript= let os: string = '' if (navigator.userAgent.indexOf('Win') != -1) os = 'win' if (navigator.userAgent.indexOf('Mac') != -1) os = 'mac' if (navigator.userAgent.indexOf('X11') != -1) os = 'x11' if (navigator.userAgent.indexOf('Linux') != -1) os = 'linux' ``` ::: 6. `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 go in to edit the title - but the problem with that is we lose the ability to reload (which `Ctrl + Shift + R` also handles). First in the useEffect on load, you should ensure that your browser is listening for `keydown` events. This uses `addEventListener`, you can also set it up to listen for `pointerdown`, `pointerup` etc. events! :::info 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**](https://developer.mozilla.org/en-US/docs/Web/API/EventListener). ::: ```typescript= document.addEventListener('keydown', nodeKeyHandlers) ``` Once you have added that you should add the following switch statement into your `nodeKeyHandlers`. 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. ```typescript= // key handlers with no modifiers switch (e.key) { case 'Enter': if (editingTitle == true) { e.preventDefault() setEditingTitle(false) } break case 'Escape': if (editingTitle == true) { e.preventDefault() setEditingTitle(false) } break } // ctrl + shift key events if (e.shiftKey && e.ctrlKey) { switch (e.key) { case 'R': e.preventDefault() setEditingTitle(true) break } } ``` # Checkoff - Show a TA that you have a `tiptap` text editor with buttons to make the text bold and italic. - Show a TA that you are able to rename a node AND that the change persists - Have a dazzling day 😎 <div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:200px" src="https://media2.giphy.com/media/8aM0z4EOqAgPZIYprz/giphy.gif" /></div>