---
title: Assignment 3 | Editable Nodes
tags: assignment
---
<span style="font-size: 50px;">**Assignment 3: Editable Nodes**</span>
:::info
**Released: October 13th**
**Due: November 2nd, 11:59pm ET**
:::
:::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**
Now that our donuts are fried, it's time to add some glaze 😎. In this assignment, we will be making the nodes in our hypertext system editable. This is a major step forward—our previously static node content will now be live and useful for a variety of applications.
A large part of the assignment will be implenting a rich text editor for text nodes. This is very open-ended, and there are many design decisions for you to make here! In addition, you will implement resizable image content for image nodes. We have included TODOs in the codebase indicated, but don't feel bound by these TODOs, they are simply suggestions.
:::danger
**Keep in mind the 30-minute rule** – if you can’t get something that appears straightforward to work in 30 minutes, it may not be your problem. Don’t be afraid to send a message in Slack if something unexpected comes up!
:::
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:500px" src="https://c.tenor.com/yrow-omq9icAAAAd/glazing-doughnut.gif" /></div>
:::success
**Note**: Assignment 3 uses the same codebase as Lab 3
:::
## **Checklist**
- [ ] **Part 1: Rich-text editor**
- [ ] Respond to design questions
- [ ] Add at least three extensions to your `tiptap` editor
- [ ] Update anchors based on the changes to the text
- [ ] Delete anchors based on the changes to the text
- [ ] Make the editor visually appealing
- [ ] **Part 2: Resizable images**
- [ ] Implement the ability to update an images `width` and `height`
- Make necessary changes to the `INode` interface
- Update the images `width` and `height` in the database
- [ ] **Part 3: Deployment**
- [ ] **Commenting**: Make sure your code is well commented; we recommend you do this as you code!
- [ ] **Capstone / Extra Credit**
# **Demo**
You can find a demo of the MyHypermedia application [here](https://hypertext-unit3-demo.pages.dev).
Note: It is entirely possible that there will be some unexpected behavior in this application as it is not set up to handle concurrency properly. This means that the application might behave weirdly if multiple users are interacting with the demo at the same time.
# **Commenting**
:::warning
**Commenting is worth 5% of the assignment grade.**
:::
For this assignment (and assignments going forward) we will be expecting good software engineering practices. That means comments! If you are ever writing code where it's not obvious **why** or **how** something is being done, you should add a comment. In addition, any new React component or complicated helper function should have a comment at the top summarizing what it does.
For normal comments, you can use `//`. For comments at the top of components or functions, use the JSDoc format (`/* Your comment here */`).
:::warning
You **do not** need to comment stencil code. **Only comment code that you have written.**
:::
We recommend writing comments as you code! Writing comments can help clarify for yourself what exactly you are trying to do. In addition, commenting as you code ensures that you don't have to go back and comment everything at the end.
# **Part 1: Rich-text editor**
:::warning
**Implementing and designing your rich-text editor is worth 55% of the assignment grade.**
- Design (`README.md`): 10%
- Rich-text implementation: 45%
:::
## **Design**
<!-- :::success
**Design Decision 1**: WYSIWYG vs. Markup
:::
The first major design decision you have to make is whether you want to create a What You See Is What You Get (WYSIWYG) editor, or an editor that uses a markup language (in this case, Markdown).
A WYSIWYG editor is something like Google Docs -- there is no separation between how the user inputs content and how the content is "rendered".
In Markdown, on the other hand, the user writes something like:
```
# My Project
## My first heading
This project is **awesome**
## My second heading
Here's some `code`
```
And Markdown renders it in HTML as:
<div style="text-align:left; margin-bottom: 20px"><img style="border-radius:5px; width:300px" src="https://i.imgur.com/lEJ0QzW.png" /></div>
As for which one to implement, it depends on what you want your text editor to be used for! As an example, WSYIWYG makes sense for Google Docs because it needs to be easy to use for all audiences, while README files on Github use Markdown by default since their users are mostly programmers, and README files don't need complicated fonts or styling. As you can see by this [Stack Exchange thread](https://ux.stackexchange.com/questions/36980/wysiwyg-vs-markdown-is-there-a-middle-ground), there is no one right answer!
:::info
Many consider `Markup` languages to be a thing of the past and something that we should try to move away from (speak to `Norm`* or `Andy` about this - they have plenty to say!). There has recently however been a resurgance of `Markup` enthusiasts ([**Sample 1**](https://www.gotypewriter.com/blog/2020/6/wysiwyg-vs-markdown), [**Sample 2**](https://dev.to/practicalprogramming/advantages-of-document-markup-languages-vs-wysiwyg-editors-9f6)).
In the end of the day, it comes down to the use case, and that is why you will be allowed to decide between implementing a `Markup Language` or `WYSIWYG` rich text editor!
:::
:::info
:::spoiler ***Norm's modified opinion**
Not only do professors “profess,” during class, but they also learn — so I have modified my opinion of markdown.
I have always believed that one should be able to declaratively (semantically) tag sections of a document. But I have also believed that typography and layout is important to draw readers into and guide them through a document. Back before WYSIWYG was invented, one used markup commands and then had to compile them and print them out on paper to see what they looked like. (I literally used 1 mile of paper for drafts of a journal article!) But when WYSIWYG came to the fore -- early Microsoft Word had style sheets (declarative markup) for example -- few people used it, since it was a somewhat hidden feature. It also took extra thought to think about what type of entity something was when you could simply select something and make it bold or italic or larger, etc. directly. Most people didn't even understand that you could apply different style sheets to the same tagged document (e.g, apply the Science Journal style-sheet and the Cell Journal style-sheet to comply with their typographic conventions)
Markdown on its own -- write it now and sometime later it will be rendered -- seemed a step back. But I can see the power of hackmd - it is the power of markup AND WYSIWYG. Even if one used stylesheets in a WYSIWYG editor, you had tagged something but after that the tag usually invisible, so you one really never saw the semantic structure of your document. Hackmd lets you essentially see the “recipe” for the document on the left and the WYSIWYG version in parallel on the right. (If you remember in an earlier class, the Symbolics Document Examiner/Concordia in the 1980s did the same thing. )
Bottom line: I see the value and appeal of the hackmd version of markdown. Old dog has learned new trick. ;-)
:::
-->
You saw how we set up a simple [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) editor with **Bold** and *Italic* in Lab 3. In this assignment, you'll be extending your text editor to support additional extensions.
To determine which features to include, you should consider what use case you'd like to support. You can choose whatever features you'd like to support your use case; the only requirement is that you use at least three additional `tiptap` styles (excluding bold, italic, and link), and that there are buttons to toggle your chosen styles.
Here are some examples of different kinds of text editors, all with very different features and use cases:
- Google Docs (page breaks, basic tables, font color)
- Notion (code snippets, headings, quotes)
- Facebook (mentions, hashtags, photos)
- VSCode (automatically colors text based on language)
:::success
**TODO**
In your `README.md`, you should write **4-6 sentences** on your implementation of your text editor including:
* What use case are you trying to address?
* Why are the features you chose suitable for your use case?
:::
## **Using Tiptap**
In this assignment, you'll become more familiar with `Tiptap`, the text editor tool introduced in lab that's a wrapper around `Prosemirror`. Make sure you refer to the `Tiptap` [documentation](https://tiptap.dev) in case you have any questions.
At a high level, the way `Tiptap` (and `Prosemirror`) work is that they fragment the content of the text editor into `Nodes` (==a different use of the term to what we have used throughout the course - don't get confused!==).
Each `Node` represents a snippet of text which can be styled with the use of a `Mark`. A `Mark` is a piece of information that can be attached to a node, such as it being emphasized, in code font, or a link. Essentially, a `Mark` is a specific style for a `Node`. One `Node` can have multiple `Marks`, for example it can be both bold and italicized.
<!-- :::success
:::spoiler **What is a `Mark`?**
A mark is a piece of information that can be attached to a node, such as it being emphasized, in code font, or a link. It has a type and optionally a set of attributes that provide further information (such as the target of the link). In Tiptap, we do not have to create marks, they are created for us (eg. `toggleBold` handles the mark for bold). In Prosemirror, marks are created through a Schema, which controls the types that exist and what attributes they have.
:::
:::success
:::spoiler **What is a `MarkType`?**
Marks are tagged with type objects that indicate whether it is a bold mark, link mark, etc.
::: -->
## **Implementation**
To implement your rich text editor you will need to make changes to at least the following files:
:::success
**`TextContent.tsx`**
Creates your full text editor.
:::
:::success
**`TextMenu.tsx`**
Creates your text editor menu.
:::
**We suggest the following roadmap:**
1. Decide on a use case.
2. Decide which style options to use for that use case (minimum 3 style options in addition to bold, italic, link).
3. Add buttons for your style options to your TextMenu; you should reference the lab and the `tiptap` [documentation](https://tiptap.dev/introduction) to learn how to do this!
4. Decide how frequently you want to update the database when text is edited.
5. Handle updating the node content in the database when text is edited.
6. Handle updating database anchors as you edit text.
7. Handle deleting database anchors as you edit text.
8. Handle updating/deleting database anchors as you delete links.
9. Make your text menu and text editor visually appealing.
:::warning
:::spoiler **Optional**: Use`Prosemirror` instead of `Tiptap`
If you feel that you would like to gain experience and learn to use `Prosemirror` without `Tiptap`, you are welcome to do so. The learning curve for `Prosemirror` is a significant one though, especially if you want to customize exactly how the extensions work.
The expectations for `Prosemirror` are no different. We expect that you have at least three different types of `Marks` in addition to bold, italic, and link.
**Note:** TAs will not be able to help with `Prosemirror` issues
:::
<!-- ### **WYSIWYG**
In **Lab 3** you made a very basic WYSIWYG menu with the ability to toggle the **Bold**, *Italic* properties of a text selection. Note that the implementation in the demo **does not** meet minimum functionality, you are expected to create a useful WYSIWYG rich text editor. The important thing here is that the WYSIWYG editor works and that you can justify your design choices towards a use case. You can use any extension you would like, as long as you are able to justify it!

-->
<!--
### **Markdown**

The stencil in `TextEditorTools` component currently returns nothing, but you may want to rewrite it such that it returns an editor where you edit the Markdown itself, remember that Markdown does not need any formatting (and therefore no extensions are necessary - unless you want!). This does not necessarily need to be a `Remirror` editor, it could be a `ChakraUI` [TextArea](https://chakra-ui.com/docs/form/textarea). As long as what you write in your markup editor is reflected in the `Remirror` component rendered in `TextEditor.tsx`.
The helper method `markdownToHtml` which takes `Markdown` text and turns it into `HTML` will be useful when you implement this, as is the `Remirror` method `context.setContent(...)` - look [**here**](https://remirror.io/docs/faq/) for documentation on how to use it!.
You may also want to make `Remirror` immutable which you can do by passing a prop to the component as follows:
```typescript=
<Remirror editable={false}/>
```
**Extensions for Markdown**
You will definately want to use the `MarkdownExtension` (https://remirror.io/docs/api/extension-markdown).
-->
### **Updating node content**
The stencil code for this project includes the functionality you implemented in `Assignment 2`, which is starting and completing links from text. What you have to implement is updating the database when a user edits the content of a text node.
:::info
Remember the `handleUpdateTitle` method we wrote in Lab 3? You will need to do something similar to update the INode `content` field.
:::
The [Editor documentation](https://tiptap.dev/api/editor#get-html) explains how to extract content from the text editor so you can store it to the database.
:::success
**TODO: Decide how frequently you update the editor's content!**
It's also up to you how often to send changes to the database while a user is editing. Sending changes often is necessary for real-time collaboration, but is not essential for this assignment. Sending an update on every change is reasonable but may lower performance.
Here are some possibilities that you can consider (there is no correct answer):
- Every time any character in your text editor is changed, update the database.
- Autosave at different time increments. `setInterval` would be a useful method if you decide this approach. Documentation is available [**here**](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
- Add a `keydown` event handler or a `Save` button that saves the content to the database each time you trigger it either by clicking or using the keyboard shortcut.
:::
<!-- #### **Helper methods you may find useful**
You don't need to use all of these methods to acheive full functionality, but you may find them helpful.
:::success
:::spoiler **`getState`** - get content in Remirror editor inside of Remirror context
#### How it could / should be used:
This can only be used inside of a `Remirror` context, so `TextEditorTools.tsx` would be a place to start.
#### Syntax / Import:
```typescript=
const context = useRemirrorContext()
const { doc, schema } = context.getState()
```
#### Input:
No input arguments
#### Output:
The `EditorState` which contains the `Schema` and `ProsemirrorNode` for the current state of the document.
Type: `EditorState`
:::
:::success
:::spoiler **`prosemirrorNodeToHtml`** - convert from Prosemirror node to HTML string
#### How it could / should be used:
This method converts a Prosemirror node into an HTML string.
Documentation: https://remirror.io/docs/faq/
#### Syntax / Import:
```typescript=
import { prosemirrorNodeToHtml } from 'remirror';
const htmlString = prosemirrorNodeToHtml(state.doc);
```
#### Input:
prosemirrorNode to be converted into an HTML string
Type:`ProsemirrorNode`
#### Output:
String of the HTML version of the prosemirrorNode
Type: `string`
:::
:::success
:::spoiler **`markdownToHtml`** - convert from Markdown to HTML string
#### How it could / should be used:
This method converts a `Markdown` string to HTML and could be useful if you are creating a `Markdown` rich text-editor.
#### Syntax:
```typescript=
markdownToHtml(markdown: string):string
```
#### Input:
`Markdown` formatted string
Type: `string`
#### Output:
`HTML` formatted string
Type: `string`
:::
:::success
:::spoiler **`updateNode`** - update node in database
#### How it could / should be used:
If you want to update something on the database! Remember how we updated the INode `title` property in Lab 3.
#### Syntax:
```typescript=
await NodeGateway.updateNode(nodeId: string, properties: INodeProperty[]):Promise<IServiceResponse<INode>>
```
#### Input:
Argument 1:
`nodeId` of the node whose metadata should be updated
Type: `string`
Argument 2:
List of `INodeProperty` elements composed of the property to be updated.
Type: `INodeProperty[]`
#### Output:
Type: `Promise<IServiceResponse<INode>>`
:::
-->
### **Updating anchors as you edit text**
When text is edited, visual updates will occur automatically, but each of our anchors may also need to be updated in the database. You can imagine if we have the following two states for our text content:
**Before edit:**
> Donuts are my favourite food. I love edible donuts and <span style='color:blue'>editable text</span>.
<span style='color:blue'>editable text</span> is an `IAnchor` with the following `ITextExtent`
```typescript=
{
type: 'text'
startCharacter: 56
endCharacter: 69
text: 'editable text'
}
```
**After edit:**
> Donuts are not my favourite food. I only love <span style='color:blue'>editable text</span>.
Now the `ITextExtent` of `IAnchor` <span style='color:blue'>editable text</span> should be updated as follows.
```typescript=
{
type: 'text'
startCharacter: 46
endCharacter: 59
text: 'editable text'
}
```
How should we go about making that update for all of the `IAnchor`s in our text content? We start by iterating over all of our link marks in the editor.
A link mark is a region of text surround by an HTML anchor tag. For the above snippet the `editable text` anchor would look something like this (in HTML):
```htmlmixed=
<a href='http://localhost:3000/node.k20d1adj/' target='anchor.kvamjx28' >editable text</a>
```
Note that the `target` is the `IAnchor` anchorId. This is important because it allows us parse the `IAnchor` object from the `target` of the link mark. If you have trouble parsing, we recommend you print out the `link` object so you can see exactly what the `link` object looks like and so that you can access the `target`.
We also have regular links to external pages, that would look something like the following:
```htmlmixed=
<a href='www.google.com'>www.google.com</a>
```
When updating our `links` we specifically are only interested in the links that have a `target` starting with `anchor`.
**Looping through `marks`**
To loop through `link` marks in the text editor, you can first iterate through all of the Prosemirror nodes in the editor as follows
```typescript=
editor.state.doc.descendants(function(node, pos, parent, index) {
// do something
})
```
Inside of the `descendants` callback function, you can get all of the marks on the node by using `node.marks`. From there, you should filter out marks with `mark.type.name != 'link'`.
Once you have a list of link marks, you should determine if the link mark represents an internal link and update the corresponding anchor in the database if necessary.
:::info
**Tip**: You may find `pos` and `node.text` helpful for updating the anchor extent and text.
:::
For more information on Prosemirror nodes, you can reference the documentation [here](https://prosemirror.net/docs/ref/). We also recommend using console.log() to understand the properties of Prosemirror objects such as nodes and positions.
Within the stencil code, you can refer to `FrontendAnchorGateway.ts` for useful methods for finding and updating anchors.
<!-- :::info
**Promise.all**
Documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You may find the function Promise.all helpful when you have to make multiple database requests. Instead of awaiting the asynchronous request inside of the loop for each mark, you can instead add the `Promise` (eg. `AnchorGateway.updateExtent(...)` is a `Promise`) to an array. You can then pass that promise into `Promise.all` to update all of the anchors concurrently!
::: -->
### **Deleting anchors as you edit text**
We also want to delete anchors when they are no longer necessary.
For example, if your text content was:
> I think donuts from <span style='color:blue'>Austria</span> are more tasty than donuts in the <span style='color:blue'>United States</span>.
Then, on our database the Node has two `IAnchor` objects, one for <span style='color:blue'>Austria</span> and the other for <span style='color:blue'>United States</span>.
If you edit your Node content so that it becomes:
> I think donuts from <span style='color:blue'>Austria</span> are super tasty.
You now have to delete the anchor in the database associated with United States.
Too see which anchors you need to delete, you should compare the `anchors` in the database from the ones that are currently in the editor. If an anchor in the database but not in the editor, then it should be deleted. You could combine this with the function where anchors are updated.
You can refer to `FrontendAnchorGateway.ts` for useful methods for finding, updating, and deleting anchors.
### **Updating anchors as you delete links**
You can decide what you'd like to do with anchors that no longer have any links associated with them ('orphan anchors'). You can delete them, turn them into highlights, etc.
**Whatever you decide to do, you should ensure that clicking on the anchor no longer brings the user to the node it linked to.**
<!--
#### Helper methods you may find useful
You don't need to use all of these methods to acheive full functionality, but you may find them helpful.
:::success
:::spoiler **`findChildrenByMark`**
#### How it could / should be used:
Takes in a `prosemirrorNode` and a `MarkType` object and returns an array of `NodeWithPosition` objects which are of the following type:
```typescript=
{
node: ProsemirrorNode
pos: number
}
```
Where `pos` indicates the start position of that particular mark. This is useful for getting the link `Marks` from the current state of the document.
Documentation: https://remirror.io/docs/api/core-utils.findchildrenbymark
#### Syntax / Import:
```typescript=
import { findChildrenByMark } from 'remirror';
const links:NodeWithPosition[] = findChildrenByMark({ node: ProsemirrorNode, type: MarkType })
```
#### Input:
Object containing the `Node` and the `MarkType`
Type:`ProsemirrorNode`
#### Output:
Array of `NodeWithPosition` which gives the `pos`, which is the start position of the `Mark` and the `Node` that the mark is contained within.
Type: `NodeWithPosition[]`
:::
:::success
:::spoiler **`filter`**
#### How it could / should be used:
Returns the elements of an array that meet the condition specified in a callback function.
#### Syntax / Import:
The following example would return a list of all of the marks where the `MarkType` is bold.
```typescript=
const bold = node.marks.filter((m: Mark) => m.type === bold)
```
#### Input:
Callback function that returns a boolean
Type: `() => {boolean}`
#### Output:
The elements of an array that meet the condition specified
Type: `Array`
:::
:::success
:::spoiler **`AnchorGateway.updateExtent`**
#### How it could / should be used:
This should be used to update the anchor. It will return a succesfull response if the update is succesfull, otherwise it return a failure response.
#### Syntax / Import:
```typescript=
await AnchorGateway.updateExtent(anchorId: string, extent: Extent)
```
#### Input:
Argument 1: The anchorId of the IAnchor object whose extent should be updated.
Argument 2: The new Extent object for the given anchor
#### Output:
The updated `IAnchor` object with the new extent.
Type: `Promise<IServiceResponse<IAnchor>>`
:::
:::success
:::spoiler **`AnchorGateway.deleteAnchors`**
#### How it could / should be used:
#### Syntax:
```typescript=
await AnchorGateway.deleteAnchors(anchorIds: string[]):Promise<IServiceResponse<{}>>
```
#### Input:
Array of anchorIDs to be deleted.
Type: `string[]`
#### Output:
Empty `IServiceResponse` object
Type: `Promise<IServiceResponse<{}>>`
:::
It's possible that your implementation of updating and and deleting anchors will involve multiple sequential asynchronous functions. It may be helful to refer to the Lab 1 handout section on asynchronisity.
-->
# **Part 2: Resizable Images**
:::warning
**Implementing resizable images is worth 30% of the assignment grade.**
:::
Until now, our image nodes have had static content. In this assignment, you will implement resizable images -- you can imagine how this infrastructure could be extended to allow fully editable images with effects, etc.
For this assignment you are expected to implement a simple editor which changes the images `width` and `height`. MongoDB is great, and lets us really easily change and add new metadata to objects.
:::info
Elements like `<img>` and `<video>` are sometimes referred to as replaced elements. This is because the element's content and size are defined by an external resource (like an image or video file), not by the contents of the element itself. You can read more about them [**here**](https://developer.mozilla.org/en-US/docs/Web/CSS/Replaced_element). This will be particularly relevant if you want to implement something using `videos` or `iFrames` in your Final Project.
:::
## **Task Overview**
You should be able to adjust the `height` and `width` of the image, and revert back to the original height and width. You have complete creative control as to how you do this, the important thing is that you handle the updated width and height in the database and that the updates are reflected in the database when they are changed. Currently our `INode` object knows nothing about the width and the height of images.
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:800px" src="https://i.imgur.com/hLx2eME.gif" /></div>
**Demo**
In the demo we have made it possible to change the height and width in the following ways:
- With `Inputs` that the user can change. The `ChakraUI` [NumberInput](https://chakra-ui.com/docs/form/number-input) component was used to create the `Input` text options.
- With a drag handler on the bottom right of the image
Updating the `height` and `width` essentially crop the images. If you decide to have it such that the image gets scaled instead of cropped, that's fine, as long as you justify your design decision!
**We do not expect you to do anything with `links` and `anchors` when you are implementing this!**
:::info
We are using Chakra components because they have a lot of useful functionality built on top of HTML elements, but you can also make your own components or use the standard HTML `<input>` component, documented [**here**](https://www.w3schools.com/tags/att_input_type_number.asp).
:::
We have handled reverting with a `Reset crop` button. Think about how you could further augment the `INode` object in order to implement this!
:::warning
You are **not expected** to replicate the demo exactly and you do not need to be able to change the `width` and `height` in more than one way. e.g. just using a input box, or a slider is completely fine!
:::
## **Implementation**
To implement this, you will need to make changes **at least** in the following files:
- `ImageContext.tsx`
- `client/src/types/INode.ts`
- `server/src/types/INode.ts`
- `server/src/types/INodeProperty.ts`
<!--
:::warning
**Note**: Do not forget to update `allNodeFields` and `INode` in the `server` folder as well as the `client` folder!
::: -->
**We suggest the following roadmap:**
1. Decide how you want to update the image's `width` and `height`
2. Modify your `INode` object to support the additional metadata that it needs
3. In `createNodeUtils.ts`, handle initialising an image node with the correct, updated metadata
4. Implement a way to update the `width` and `height` property of images
5. Handle updating the `width` and `height` metadata that you have created as you modify the image such that these values are changed in the database.
#### **Helper methods you may find useful**
:::success
:::spoiler **`getMeta`**
#### Where is it?
In `createNodeUtils.ts`
#### What it does:
Allows you to fetch the image width and height, normalized to a height of`300px`.
#### Syntax:
```typescript=
await getMeta (imageUrl: string):string
```
#### Input:
imageURL which is the URL of the image that we are trying to import
Type: `string`
#### Output:
Typescript object consisting of the height and width (both normalised such that the height is always `300`) returning the following structure:
```
{
normalizedHeight: number
normalizedWidth: number
}
```
So if you wanted to access the `normalizedHeight` you could do:
```typescript=
getMeta('www.exampleimage.png').normalizedHeight
```
:::
:::success
:::spoiler **`FrontendNodeGateway.updateNode`**
#### How it could / should be used:
If you want to update something on the database! Remember how we updated the INode `title` property in Lab 3.
#### Syntax:
```typescript=
await FrontendNodeGateway.updateNode(nodeId: string, properties: INodeProperty[]):Promise<IServiceResponse<INode>>
```
#### Input:
Argument 1:
`nodeId` of the node whose metadata should be updated
Type: `string`
Argument 2:
List of `INodeProperty` elements composed of the property to be updated.
Type: `INodeProperty[]`
#### Output:
Type: `Promise<IServiceResponse<INode>>`
:::
<!-- ## **Grading** -->
# **Part 3: Deploying**
:::warning
**Deployment is worth 10% of the assignment grade.**
:::
You already did this in Assignments 1 and 2, so you should be pros by now! If you weren't able to do this in previous assignments, start this step early so you don't run into unforseen problems too late on.
If you'd prefer to deploy both your frontend and backend on just `Heroku` or just `Firebase`, feel free to do so.
:::info
**Reminders before deploying**:
- Don't forget to remove `console.log()` debug statements
- Remember to replace 'http://localhost:3000/' in `TextContent.addAnchorMarks()` with your frontend URL
:::
## **Deploying Backend**
:::warning
Note: These deployment instructions are the same as for previous assignments and are just included for your convenience.
:::
First, we'll want to deploy our backend service to Heroku. The first step is to register an account and install Heroku CLI. To do that, please follow the link here: [Getting Started on Heroku with NodeJS](https://devcenter.heroku.com/articles/getting-started-with-nodejs).
:::success
**Setting up the environment**
- Complete the "Introduction" and "Set up" portion of the [tutorial](https://devcenter.heroku.com/articles/getting-started-with-nodejs) only.
- Login to Heroku CLI with your account.
:::
Now you should have the Heroku environment ready to go. We can start deploying our backend code. Since we have our server and client in one repo, it's crucial that we only deploy a subfolder of the whole repository.
:::success
**Creating instance and deploying**
- Create a new Heroku instance with `heroku create`
- Commit your changes
- In the root directory of the repository, deploy the `server` subfolder to Heroku instance:
`git subtree push --prefix server heroku master`
:::
We have deployed our code to Heroku! But now it will not work yet, because we have not configured environment variables and our application does not know what port to start on.
:::success
**Configuring environment variables**
- Login to Heroku web portal: https://id.heroku.com/login
- Navigate to the instance you just created, and select *Settings* tab.
- Navigate to `Config Vars` section, and click `Reveal Config Vars`.
- Fill the table with your own `DB_URI`, keeping everything else the same as the image below.
:::

Now, if you refresh your backend instance, you should see that "MyHypermedia Backend Service" is rendered on the DOM. Moving on to deploying frontend!
:::warning
**Commonly Asked Questions**
*Q: What if I already have a Heroku account? Do I need to create a new one for this course?*
A: No, you can use your personal account for Heroku. If you run out of your instance limit, consider registering a new account.
*Q: The app works locally but Heroku says "Application Error". What's wrong?*
A: Go to Heroku dashboard and select your instance. Click `More` button and select `View logs` option. You will see the console output and debug from there.
Check whether you have configured environmental variables (config vars) correctly.
Note that Heroku does not install `devDependencies` if you have used your own npm package; consider moving it to `dependencies` in `package.json`.
:::
:::success
**TODO:** Put your backend Heroku URL in the assignment `README.md`
:::
## **Deploying Frontend**
:::warning
Note: These deployment instructions are the same as for previous assignments and are just included for your convenience.
:::
After deploying backend, we will be deploying our frontend using Firebase, a Google service. Before we start, we need to change the endpoint in the frontend codebase to point to the backend service we just deployed. Let's do it!
:::info
**Where can I find my backend deployment link?**
Click the "Open App" button in Heroku instance page, your backend app will open in a new window.
:::
The first step is to change the endpoint in Frontend to point to the remote backend service.
:::success
**Changing the endpoint**
- Navigate to `client/src/global/endpoint.ts`
- Update the `endpoint` variable to point to your backend deployment. Do NOT omit the trailing `/` in the URL. This will change the end point for all frontend gateways you wrote.
For example, `export const endpoint = 'https://vast-caverns-62320.herokuapp.com/'`
- Commit your changes
:::
The second step is to install the Firebase CLI.
:::success
**Installing Firebase CLI**
Follow the instruction [here](https://firebase.google.com/docs/cli) to install Firebase CLI.
:::
Next, let's log into the Google Firebase using your personal Google Account.
:::warning
The account you use **must not be affliated with Brown University**. Use your personal Google Account, or register a new one. Brown University accounts does not allow users to create Firebase apps.
:::
After we log in, it is time to create and configure the Firebase app!
:::success
**Create and configure Firebase app**
Note: if you haven't used this account for Firebase before (i.e. in the first projects), you will need to accept the terms on the [Firebase console](https://console.firebase.google.com/) first before proceeding.
- Navigate to `client` folder in terminal. This is important!
- Create an Firebase app using `firebase init`
- When CLI prompts: `Are you ready to proceed?`, enter `Y` and hit enter
- In the next step, use up/down arrow and space to select option `Hosting: Configure files for
Firebase Hosting and (optionally) set up GitHub Action deploys`
- In the next step, select `Create a new project`
- Specify your project id and project name.
- When CLI prompts: `What do you want to use as your public directory?`, enter `build`.
- When CLI prompts: `Configure as a single-page app?`, enter `Y`.
- When CLI prompts: `Set up automatic builds and deploys with GitHub?`, enter `N`.
- Wait for the initialization to complete.
:::
Now we have created a Firebase instance on the cloud! Since we are deploying our React application, we will use the `production` version of React. How do we do that?
:::success
**Build production React and deploy**
- Navigate to `client` folder in terminal.
- Build production version of React using `yarn build`. This would compile your React to HTML and JS files to a folder called `build`. We will deploy `build` folder.
- Deploy your compiled frontend to Firebase using `firebase deploy`.
:::
All done, have fun! If everything runs smoothly, you should be seeing your app deployed on Firebase. Firebase would give you the URL of the app upon successful deployment.
You just deployed a fully-fledged hypertext system. **Open your frontend deployment, and test it as a user!**
:::success
**TODO:** Put your frontend Firebase URL in the assignment `README.md`
:::
# **Capstone / Master's Requirement**
:::warning
If you are taking this course for capstone or master's credit, you are expected to do **at least ONE of the following features**. You can complete additional features for extra credit.
:::
## **History**
Implement `history` for the editable text. Think about how we might want to store this on the database and the way in which we might want to access that content. Think about what data structure you would want to add to the database to support this.
## **Image Editing**
Add other properties to your images like `scale`, `offset`, or `rotation`. Note that `rotation` will be very difficult to align with the `links` that are rendered on top of the image, so you are not expected to.
Feel free to use a Node package to help you with your implementation of this!
## **Want to do something else?**
No problem! Before you start, send a message in Slack to a TA to get it approved. **In order for it to be considered capstone / master's / extra credit you must get it approved first!**
# **Handin**
Fill out the `README.md` as per the following instructions and submit your assignment to `Gradescope`.
## **`README`**
You `README.md` should include the following:
- **4-6 sentences** responding to the design questions
- **Other notable design choices**
- **Capstone / Master's Requirement**. An explanation of what you implemented, if you implemented anything
- **Known bugs**
- **Deployed Frontend URL**
- **Deployed Backend URL**
# **Grading**
## Design Questions - 10pts
| Task | Grade |
| ----------- | ----------- |
| Explanation of use case | 3 pts - TA will verify |
| Justification for `tiptap` extensions | 7 pts - TA will verify |
## Frontend Functionality - 75pts
#### Rich-Text Editor
| Sub-rubric | Criterion |
| ----------- | ----------- |
| Text styling </br> | **10 pts** - Has at least 3 different extensions in addition to `Bold`, `Italic`, `Link` that are justified in the design. Text editor is reasonably styled.|
| Update text content | **10pts** - Text updates in database when text is edited. There is a clear mechanism for updating (automatically, with a save button, with an interval timer) |
| Update anchors | **25 pts** - Anchors update as expected. When you reload the page, the anchors are correctly located even after making changes. Anchors are deleted as expected. Orphan anchors are handled. |
### Image Editor:
| Sub-rubric | Criterion |
| ----------- | ----------- |
| The image is resizable and there is a user friendly / intuitive way to update the `width` and `height` of the Image | **15 pts** |
| The `INode` has metadata for image height and width stored on the database | **10 pts** |
| There is a way to `Revert` / `Reset` and return to the original size of the image when it was imported | **5 pts** |
## Commenting - 5pts
| Task | Grade |
| ----------- | ----------- |
| Code is clearly commented | **5 pts** - TA will verify |
## Deployment - 10pts
| Task | Grade |
| ----------- | ----------- |
| Deploy backend | **5 pts** - TA will verify |
| Deploy frontend | **5 pts** - TA will verify |
#### Total = 100 pts
## Capstone / Master's Requirement / Extra Credit - up to 30 pts
| Task | Grade |
| ----------- | ----------- |
| Text content `history` | 10 pts - TA will verify |
| Additional options for editable images | 10 pts - TA will verify |
| Task of your own design | 10 pts - TA will verify |
:::warning
**For students taking the course for capstone or master's credit** the grade is out of `110 pts` as one of the two `Extra Credit` tasks is expected.
:::