---
title: Assignment 3 | Editable Nodes
tags: assignment
---
<span style="font-size: 50px;">**Assignment 3: Editable Nodes**</span>
:::info
**Released: October 12th**
**Due: November 2nd, 11:59pm ET**
**Late: November 4th, 11:59pm ET**
:::
<!-- 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 by `TODO [Editable]` but **do not** 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!
:::
**This is a frontend only assignment**, since we have all the backend functionality we need to update nodes and anchors!
<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>
:::warning
We have refactored our code base to support additional features for this assignment. Please build your system with the new stencil code we provided in this new *GitHub Classroom Assignment*. **Accept the GitHub repository [here](https://classroom.github.com/a/2fRsA-Dm)**.
:::
## **Checklist**
- [ ] **Accept the GitHub repository via GitHub Classroom [here](https://classroom.github.com/a/2fRsA-Dm)**
:::warning
Don't forget to add your `.env` files to both the `client` and `server` directories. They should be the same as `Assignment 2`
:::
- [ ] **Part 1: Rich-text editor**
- [ ] Design decisions for your rich-text editor
- Implement your design, using at least three `Remirror` extensions
- [ ] Update anchor extent in database when the text is edited
- [ ] Deleting anchors based on the changes to the text
- [ ] **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**
# **Debugging Tips**
As you are all far too aware by now - debugging is an integral part of writing any code. With full stack web applications that is particularly the case. Many of you will already be familiar with these tools, but in case you are not we wanted to introduce them to you to ensure that your experience debugging frontend is as enjoyable as possible!
## **Chrome Debugger**
:::warning
We know many of you use other browsers, most browsers have a debugger similar to Chrome but we are linking the tutorial for some common browser debugging platforms.
:::
If you are not familiar with the browser debugger we strongly recommend that you have a look at the following resources, they can save you plently of time when it comes to working on your frontend!
1. [Pause your code with breakpoints](https://developer.chrome.com/docs/devtools/javascript/breakpoints/)
2. [Styling your frontend](https://developer.chrome.com/docs/devtools/css/)
## **VSCode Debugger**
You can also debug directly in VSCode!
:::warning
**Note**: Before you follow any of those steps make sure you have the [Debugger for Chrome (Nightly)](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome-nightly) VSCode extension installed (Authored by `Microsoft`)
Note that we have set up the debugger for VSCode to run in Chrome.
:::
You can start with `Run` > `Start debugging` or by going to the Run and Debug sidebar (Shortcut: `Ctrl + Shift + D`) and clicking `Launch chrome against localhost`
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:300px" src="https://i.imgur.com/GeM3KV1.png" /></div>
This will open `localhost:3000` in Chrome, where you have access to all of the Chrome debugging tools as well.
Now, the useful part of using the VSCode debugger, adding `breakpoints` to your code! Let us say that we are in `ImageContent.tsx` and we are having issues accessing the anchors that we should render on our node. We want to know what `startAnchor` is and what `anchor` is in the following snippet of code for `displayImageAnchors`
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:600px" src="https://i.imgur.com/jm2OThQ.png" /></div>
Spot the 🔴 next to line `106`. What that means is that we have set a breakpoint at that line in our code. You can add new breakpoitns by click next to the line number. Now when we run our code in the debugger and go to an image, your web app will freeze at that point.
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:600px" src="https://i.imgur.com/SpUj89X.png" /></div>
You can see that it has frozen at line `108`. The menu at the top of the following screenshot (next to the `ImageContent.tsx` tab) can be used to `Continue`, `Stop`, `Restart` etc. When we are frozen at a breakpoint we can open the `Debug Terminal` and get really valuable insights as to what the variables are.
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:800px" src="https://i.imgur.com/AUtQYm9.gif" /></div>
# **A note on managing state variables**
You may have noticed how the state variables in our system are being passed from component to component, almost to the point where it is getting out of hand! You can see that just looking at `INodeViewProps` in `NodeView`. Not every web application needs to look like this, as there are technologies that a *global store* from which you can read state.
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:500px" src="https://i.imgur.com/N1LM1u5.png" /></div>
Most large `React` web applications will use something called [**React Redux**](https://react-redux.js.org/). It lets your React components read data from a Redux store, and dispatch actions to the store to update state. Essentially what this means is rather than having to inherit state variables and functions in the way that we are currently, each React component has access to a global Redux store, this greatly simplifies how state variables that should be globally accessible, for example `currentNode` or `selectedAnchors` would work in a web application.
:::info
**Note:** We will not be teaching React redux in the assignments for this course due to the smaller scale of our web application. We however wanted to introduce it to you so you can get an idea for how state variables might be handled in larger code bases than `MyHypermedia`.
:::
# **Demo**
You can find a demo of the MyHypermedia application [here](https://basic-hypermedia.web.app/).
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.
If there is anything that is unclear in this assignment, or you find a bug in our code base, please let us know in Slack!
:::warning
**Known bugs in TA implementation**
Here is a list of known bugs in our implementation. You are not expected to have these bugs fixed in your system. We will update this list if more bugs are found.
- In demo, Firebase does not support live `React` routing. If you navigate directly to `https://basic-hypermedia.web.app/folder.kvagv8kf/image.kvagvqn8/` it will give a `404: Page not found` error. When running on `localhost:3000` it should work!
- Text content does not update when you switch to another text node. Fix in main post on Slack [**here**](https://cs1951vfall2021.slack.com/archives/C02D94WAM9S/p1635798226088400).
- Ignore the following `console` error. It does not hinder performance.
<div style="text-align:center; margin-bottom: 20px"><img style="border-radius:5px; width:500px" src="https://i.imgur.com/G8SbAUw.png" /></div>
:::
# **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`): 15%
- Rich-text implementation: 40%
:::
## **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, though some of them — like Microsoft Word had style sheets (declarative markup), few people used it, since it was a somewhat hidden feature and took extra thought when you could simply select something and make it bold or italic or larger, etc.
Markdown on its own - write it now and sometime later it will be rendered seemed a step back. But I see the power of hackmd - it is the power of markup AND WYSIWYG. Even if one used stylesheets in a WYSIWYG editor, what you had tagged something was usually invisible, so you didn’t really easily view 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. ;-)
:::
:::success
**Design Decision 2**: Which features?
:::
Which features do you want your rich-text editor to support? This is open-ended; the only requirement is that you use at least three `Remirror` extensions excluding the `BoldExtension`, `ItalicExtension`, `UnderlineExtension` and `LinkExtension`.
You can find the demos of the available `Remirror` extensions in this [storybook](https://remirror.vercel.app/?path=/story/introduction--introduction). The code is not available in the `storybook` but instructions are available [here](https://remirror.io/docs). You saw how we set up a simple WYSIWYG editor with **Bold** and *Italic* in ==Lab 3==, now you should extend the text editor to support other extensions as well!
Here are some examples of different kinds of text editors, all with very different features:
- Google Docs (Page breaks, basic tables, font color)
- Notion (Code snippets, headings, quotes)
- Facebook (Mentions, hashtags, photos)
- VSCode (Automatically colors text based on language)
- HackMD (Markdown editor)
- Overleaf (LATEX Markup editor)
:::success
**TODO**
In your `README.md`, you should write **6-10 sentences** on your implementation of your text editor including:
- Whether you built a`Markup` or `WYSIWYG` text editor, and why? What **use case** are you trying to address?
- Why the editor you chose is suitable for your use case?
- Which `Remirror` extensions you used and why you used them?
:::
## **Using Remirror**
In this assignment you will need to become familiar with `Remirror`. It is an incredibly powerful tool for text editing. Unlike editors like [RichTextEditor](https://richtexteditor.com/) and [Draft.js](https://draftjs.org/), Remirror has a lot of flexibility with regards to how you implement it and how you want it to work! Make sure you refer to the `Remirror` documentation in case anything is unclear.
Remirror has very useful commands and helpers, documented [**here**](
https://remirror.io/docs/getting-started/commands-and-helpers/).
```typescript=
import { ... } from '@remirror/react'
const commands = useCommands()
const chain = useChainedCommands()
const active = useActive()
const context = useRemirrorContext()
const helpers = useHelpers()
```
The way `Remirror` (and `Prosemirror`) work is that they fragment the text into `Nodes` (==a different use of the term to what we have used throughout the course - do not 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. There are many different types of `Marks` (and users can define their own `Marks` as well). Essentially, a `Mark` is a specific `style` for a `Node`. One `Node` can have multiple `Marks`, for example it can be ***bold and italic***.
:::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 Remirror we do not have to create marks, they are created as part of the extension (eg. `BoldExtension` handles the `Mark` for bold). In Prosemirror Marks are created through a Schema, which controls the types that exist and what attributes they have.
**Import**: `import { Mark } from 'remirror'
`
:::
:::success
:::spoiler **What is a `MarkType`?**
Like nodes, marks (which are associated with nodes to signify things like emphasis or being part of a link) are tagged with type objects, which are instantiated once per Schema. You can access the `MarkType` of a document by doing with the following syntax:
```
const bold: MarkType = schema.marks.bold
```
**Import**: `import { MarkType } from 'remirror'
`
:::
:::info
Remirror also has its own community **[Discord server](https://remirror.io/chat)**, where contributers are very active! If you want to implement something tricky and can't quite figure out how to do it - feel free to join the Discord server and ask questions there, they are very responsive and helpful!
:::
## **Implemenation**
To implement this you will need to make changes **at least** in the following files:
:::success
**`client...TextEditor/TextEditor.tsx`**
This class is the wrapper for your `Remirror` editor.
:::
:::success
**`client...TextEditor/TextEditorTools/TextEditorTools.tsx`**
This class contains the logic for your `Remirror` editor, as well as access to all of the `Remirror` helper methods.
:::
**We suggest the following roadmap:**
1. Decide which type of `Rich-text editor` you would like to implement.
2. Decide which `Remirror` extensions you want to use built around a use case.
3. Handle updating the `INode` content (see section below)
4. Implement the `Remirror` extensions that you want to add to the codebase and design it depending on the `Rich-text editor` that you chose.
5. Handle updating anchors as you edit text. We recommend writing a new method to do this, you should consider how frequently you want this to get called.
6. Handle deleting anchors as you edit text.
:::warning
:::spoiler **Optional**: Use`Prosemirror` instead of `Remirror`
If you feel that you would like to gain experience and learn to use `Prosemirror` without `Remirror`, you are more than 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` defined in your `Schema`.
**Note:** TAs will not necessarily 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.
For instance, supposed I have some links in my text content, and I add some text to the beginning of the content. I definitely need to update the `node` in the database with its new `content`. What about the anchors? Every anchor's `startCharacter` and `endCharacter` have now changed and needs to be updated!
:::info
Remember the `handleUpdateTitle` method we wrote in Lab 3? You will need to do a similar thing to update the INode `content` field. Make sure to look back at that method, but also look at the `Helper Methods` section below!
:::
You will need to decide *how* you want to store your INode `content` in the database. This could be as raw HTML, Markdown, text like for static nodes. Having a look at these `Remirror` [docs](https://remirror.io/docs/faq/) might be a good starting point!
The `stringHandler` prop passed into `UseRemirror` when you create your `Remirror` manager is what parses the content stored on the database into the `prosemirrorNode` rendered by the editor.
:::danger
**Note**: The `Remirror` explanation of `prosemirrorNodeToHtml` is incorrect, the correct version is indicated in the Helper Methods section below):
:::
:::success
**TODO: Deciding 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 posibilities that you can consider (there is no correct answer):
- 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.
- Everytime any character in your text editor is changed, update the database. This can be done using the `onChange` prop in your `Remirror` component. In `TextEditor.tsx` you may notice there is already a `onTextChange` method, wherein the extent for text is selected for creating an `ITextExtent`. The `from`, `to`, and `text` variables indicate the `selectedText`.
- 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).
:::
### **Updating anchors as you edit text**
When text is edited each of our anchors need to be updated. You can imagine if we have the following two states for our text content:
**Text Content 1:**
> 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'
}
```
**Text Content 2:**
> 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. How should we go about making that update for all of the `IAnchor`s in our text content?
```typescript=
{
type: 'text'
startCharacter: 46
endCharacter: 59
text: 'editable text'
}
```
What does a link `Mark` look like?
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='anchor.kvamjx28'>editable text</a>
```
Note that the `href` is the `IAnchor` anchorId, this is important because it allows us parse the `IAnchor` object from the `href` of the link mark. If you have trouble parsing we recommend you print out the `link` object using the VS Code debugger (see steps above), so you can see exactly what the `link` object looks like and so that you can access the `href`!
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 out `links` we specifically are only interested in the links that have an `href` starting with `anchor`.
:::success
**Looping through `marks`**
What you need is a way of looping through all of the `link` marks in the current state of text to determine the new location of each of the anchors, specifically the ones with anchors linked to other places that are also in the database. To get all of the `link` marks (including those linked to external pages eg. `https://www.google.com`) you can call `findChildrenByMark`. For each link that needs to be updated you should make a call to `AnchorGateway.updateExtent`.
**Promise.all**
Documentation: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
Since you will be creating multiple `AnchorGateway.updateExtent` requests, you should note that whenever you create multiple promises, `.all` them - that way it waits for all the promises and no error from any of them are silenced. What does this mean and how should you do this? Instead of making the asynchronous request inside of the loop for each mark, 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 a user creates an anchor on the snippet <span style='color:blue'>Donut</span>, and later deletes all the characters in <span style='color:blue'>Donut</span>, we want to delete that anchor from our database as well, because it no longer exists!
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>.
And you edit it so that it becomes:
> I think donuts from <span style='color:blue'>Austria</span> are super tasty.
:::success
**Getting the specific nodes that have been updated / removed**
When you have edited your text (see `Looping through marks` above) the state of the text content has changed and you may have removed an `anchor`.
Now in our editor we have removed the <span style='color:blue'>United States</span> `IAnchor` but it still exists on our database. Essentially what you want to do when you are checking which anchors to delete is to compare the `anchors` in the database from the ones that are currently in the editor. If they are in the database but not in the editor, then they should be deleted. You could combine this with the loop where anchors are updated.
`AnchorGateway.getAnchorsByNodeId` will be useful to get the current anchors from the database. Once you have the list of anchor to delete you can call `AnchorGateway.deletedAnchors`.
:::
## **Helper Methods / Types**
You do not **need** to use all of these methods to acheive full functionality.
:::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 **`context.getState()`**
#### 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 **`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 **`prosemirrorNodeToHtml`**
#### 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`**
#### 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 **`NodeGateway.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 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>>`
:::
:::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<{}>>`
:::
<!-- ## **Grading** -->
# **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 that, 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 stretched instead of cropped, that is accepted, 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 do it in the same way as the demo and you do not need to be able to change the `width` and `height` in more than one way. Eg. 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:
- `client...Modals/CreateNodeModal/createNodeUtils.ts`
- `client...NodeView/NodeContent/ImageContext.tsx`
- `client/src/types/INode.ts`
- `server/src/types/INode.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 and design how you want to update the images `width` and `height`
2. Modify / augment 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 the 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 it is changed in the database.
## **Helper methods**
:::success
:::spoiler **`getMeta`**
#### Where is it?
#### How it could / should be used:
In `createNodeUtils.ts` make sure that an `INode` of type `image` has the correct metadata. This method allows you to fetch the image width and height, normalised 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 **`NodeGateway.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 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>>`
:::
<!-- ## **Grading** -->
# **Part 3: Deploying**
:::warning
**Deployment is worth 10% of the assignment grade.**
:::
You already did this in `Assignment 2` so you should be pros by now! If you weren't able to do this in `Assignment 2` start this step early so you don't run into unforseen problems too late on.
If you'd prefer deploy both your frontend and backend on just `Heroku` or just `Firebase` feel free to do so!
## **Deploying Backend**
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**
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**
- 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 `N`.
- 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 / Extra Credit**
:::warning
If you are taking this course as a capstone course, you are expected to do **at least ONE of the following features**.
:::
## **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. Feel free to approach a TA if you want to discuss the design implementation for this!
## **Image Editing**
Feel free to use a Node package to help you with your implementation of this!
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 hte image, so you are not expected to.
## **Want to do something else for extra credit?**
No problem! Before you start, send a message in Slack to a TA or attend TA hours to get it approved. **In order for it to be considered for extra credit / capstone you must get it approved first!**
# **Handin**
Fill out the `README.md` as per the following instructions and submit to `Gradescope`.
## **`README`**
You `README.md` should include the following:
- **6-10 sentences** on your implementation of your text editor including:
- Whether you built a`Markup` or `WYSIWYG` text editor, and why? What `use case` are you trying to address?
- Why the editor you chose is suitable for your use case.
- Which `Remirror` extensions you used and why you used them
- **Other notable design choices**
- **Extra Credit / Capstone**. An explanation of what you implemented, if you implemented anything
- **Known bugs** (excluding those in this handout)
- **Deployed Frontend URL**
- **Deployed Backend URL**
# **Grading**
## Design Questions - 15pts
| Task | Grade |
| ----------- | ----------- |
| Justification for `WYSIWYG` vs `Markup` | 6 pts - TA will verify </br> |
| Justification for `Remirror` extensions | 9 pts - TA will verify </br> (3 pts per extension) |
## Frontend Functionality - 70pts
| Task | Grade |
| ----------- | ----------- |
| Rich-text editor implementation | 40 pts - TA will verify functionality on deployed version |
| Resizable images | 30 pts - TA will verify functionality on deployed version |
Rubric specifications for `Rich-text editor implementation`:
| Sub-rubric | Criterion |
| ----------- | ----------- |
| Has at least 3 different `Remirror` extensions excluding `Bold`, `Italic`, `Underline`, `Link` that are justified in the design </br>**Note**: Implementing `Strike`, `Subscript`, and `Superscript` may be easier to implement, but you will lose marks for your Design Question. | **10 pts** - 3+<br/>6 pts - 2 </br> 3 pts - 1 </br> 0 pts - 0 |
| Update text content | **10pts** - Text updates in database when text is edited. There is a clear mechanism for updating (automatically, wiht a save button, with an interval timer) |
| Update anchor extent locations | **20 pts** - Anchors update as expected. When you reload the page the anchors are correctly located even after making changes |
Rubric specifications for `Image`:
| Sub-rubric | Criterion |
| ----------- | ----------- |
| The image is resizable and there is a user friendly / intuitive way to update the `width` and `height` of the Image `INode` and it gets updated in the database | **15 pts** - TA will very |
| The `INode` has metadata for image height and width stored on the database | **10 pts** - TA will very |
| There is a way to `Revert` / `Reset` and return to the original size of the image when it was imported | **5 pts** - TA will very |
## 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
## 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 Capstone students** the grade is out of `110 pts` as one of the two `Extra Credit` tasks is expected.
:::