 Hey everyone, my name is Johnata, in this video we are going to create this nice and clean not taking desktop app called Notemark. Notemark features many cool functionalities such as a real-time markdown editor that every time we type some markdown content inside it, it will be immediately formatted into the proper markdown styling. Notemark also is gonna have an automatic saving system that will save the content of the nodes completely automatically. And finally we'll also persist all of our nodes as files stored in our file system, like any real nodes application does it. Before starting let me give you a quick demo of Notemark. To create a new node we can just click on the sidebar, top left button, type a name for our node and create it. On the right side we have our node editor where we can insert some markdown content inside it. You can see as our content is immediately turned into formatted markdown. We can also scroll through the list of nodes that we have already created and select the one we want to view or edit. And finally we can also remove the currently selected node just by clicking on the top right button. We're gonna build this application using Electron, a framework for building desktop application using HTML, CSS and JavaScript. We're gonna also use React for creating our UI components, TypeScript for having type safety support, Tailwind CSS for styling, Jotae, a simple and powerful React state manager and finally a third-party component called MDX Editor to integrate our node editor. So without further ado let's get started. Okay the first step we'll be generating our Electron project and for doing that we're gonna open up our terminal and let's type in yarncreate at quick start, forward dash, electron. Basically here we are using a build tool called ElectronVit which is a modern tool for generating electron application based on VIT. So here we are prone to insert our project name and we're gonna insert node dash mark, so node mark, we're gonna use React as a framework with TypeScript support and we don't want any updated plugin and any mirror proxy, great. So let's see the inside this new created application and let's install all the dependencies. So while the dependencies are installing I'm going to explain quickly how this Electron VIT works. The Electron VIT basically it scaffold an electron project by using VIT as a bundling tool. So this project are going to inherit some of the benefits of VIT like odd module replacement, odd reloading and also support for TypeScript, React, Vue, Svelte, etc. The nice thing is that our Electron project will be already scaffolded nicely so we don't have to worry about to configure properly the project because this tool will do everything for us. So the dependencies are installed so we can open up this project on the editor. So let's open up a folder, let's go under project, YouTube, node mark and let's open it up. Okay, so this is our initial Electron project. You can see there are many configuration files, there are many folders but let's get a quick overview on how this project has been scaffolded. So you can see we have some files relating to the code base configuration such as ESLint configuration or Retier configuration. We are going to see soon how to set up those files. We have an electronbuilder.yaml which is a configuration file for when we are going to have to build our application for being packaged as a desktop application. We have a specific file for managing the VIT configuration along with an electron environment and finally we have some TypeScript configuration file and here you can see that we have two separate TypeScript configuration, one for node environment and one for the web environment which is related to our browser window process. So the main core of this project structure is under the source folder where we have three main folder, the main, reload and render. In order to explain this three folder I found an image that will gonna help us understand how an electron project works under the hood. Okay, so an electron application is basically divided into two main processes, the main process and the renderer process. The main process runs in a Node.js environment and has access to the Node.js API along with the electron main process modules. This process is useful for running privileged operation such as for example accessing to the file system and perform operation such as reading a file, writing a file, deleting a file or whatsoever. So it basically is like a context isolated process to run a restricted operation. Then we have our renderer process which is basically our browser window and it basically is where our application lives. So this process is divided in two parts. On the right parts we have our entry point for our application which is the index.html which then connects to an index.js where we basically run our web framework in our case React. And we have also this left part which is called the preload script. The preload script basically have access to the restricted set of function from the Node.js API as access to the DOM API. But the most important part is that is able to access a channel called IPC. This channel is used by the preload script to communicate to the main process. And this will be one of the most important part of our application because we at some point we will want to create files, write files, delete files to store our nodes on the file system. But the only way to perform this kind of operation is to communicate our main process to do that. And the only way to communicate to this main process is using this IPC channel. So let's quickly recap. We have the main process that runs a restricted operation and the renderer process which is divided by our React application list along with the preload script. If we go back to our electron project, you can see that we have three separate folder one for each of the roles that we have seen in the previous image. So we have a folder which contain the index.js of the main process. We have a preload folder which contains all the files related to the preload script. And finally a renderer folder where our React application basically will live. So you can see that we have another source folder inside. And here we have an initial project that the application already created for us. You can see that we have the entry point which is the index.html which links to the main.jsx and this main.jsx basically runs React under the hood. And so at this point we can start to try to run this application and see what happens. We can run the command yarn dev and you can see that our application has been started. So this is our like a sample application but it's basically, you know, a desktop application completely functioning. We have some, I think that these are some links to original documentation of the electron bit pool. And yeah, it's working fine. So let's go ahead and start working on this project. Let's start by configuring our preter configuration in order to have our files formatted nicely every time we hit save and in order to having a consistent format throughout all this file. In order to do this we have to open up the .vscode folder, open up the setting.json And here I'm going to paste a basic configuration for, you know, preter, great. So that was it for this file. And if we open up the preterrc we can see that the electron bit build tool has already a preconfigured preterrc configuration. We can just use this configuration and don't worry about it. Same for the preter ignore we can leave as is. Next step will be adding some yes linked rules. As you can see here also electron bit as an initial configuration but we can enhance it a bit. So let's go ahead. So we basically add this rules configuration where we basically turn off some rules to enhance our code experience. So yeah, that was also it for this ASL in configuration. Okay next step will be configuring our main process. So let's open up main index dot yes and let's give it a look to this file. We have already some configuration going on and it is like the configuration that electron bit has applied for us and actually is actually quite complete. So let's give it a quick look for example here we are basically creating our window. We are assigning a width of 900, height of 670. We are already setting an icon for Linux environment. We are hiding the menu bar and also we are setting the web preferences. Let's give a quick look to the other configuration that has been already added. For example here we are showing the main window only when it's ready to be shown. Here for example we are denying the possibility to create a new window and this is okay for our application because we don't need any separate window beside the main one. Here we are just loading our index dot html file when everything is ready. What we are doing here is saying that when the app is ready we can start by setting the user model ID. We are creating the window. This code snippet right here is saying that every time that we click on our application we are gonna run this code and basically we are saying hey if we don't have any windows available create a new windows. This is especially useful if we have our application already opened up so if it's already opened up this dot land will be greater than zero so we don't have to create a new window. And finally we have this event that basically quit our application when all the windows are closed except on macOS and this is a common behavior for you know macOS desktop application. Anyway this file is already commented so you can take your time to read these pre-built comments that was added by the tool that we are using for creating this project. So what I'm going to do now is going inside the web preferences and basically enable the sandbox and set it to true and also enable the context isolation setting also to true. So these two properties are super important because we'll enhance the security of our application for example the context isolation will make the JavaScript context of the renderer process separated from the main process and we also have the sandbox properties which will permits out to sandbox our renderer process to make it more secure. Okay so that's it for now for the main process configuration and we can move on. Next step will be configure our reload script so let's open up reload slash index dot yes. What we are going to do here is to remove everything and let's add a custom script. So the first thing that we are going to do is to check that we are running in a context isolated environment where we are going to say if we are not in a context isolated we are gonna throw a new error which is going to say context isolation must be enabled in the browser window. Second step will be adding a try catch where we are gonna use the context bridge where we are going to expose variable called context for now we are gonna leave it empty and later on we are gonna add all the necessary things that we need here so let's also have a catch statement where we console log any possible error that can occur and the same way we need to go inside the index dot d dot yes which basically defines some global type configuration in order to have typescript support every time we want to call any of the function that will be available inside here so we don't want to export this electron API since we don't need it for this project but actually we are going to rename this to context and we are gonna have for now just an empty object but later we are gonna have some properties and function and they basically this typescript configuration and especially this context field is the same that we are basically exposing in the main world throughout this context bridge so now let's leave it like this and we can move on to the like to the core part of this project which is the renderer folder so let's close up this tab and let's concentrate in the renderer folder okay let's go ahead and let's start by taking a look at this index dot html file so this is a basic index dot html file which is the entry point for our renderer process and okay so we can start by removing some things that we we don't need like the title and this comment right here and also we can change these content security policies and adding a custom one which i'm going to copy just copy and paste it here so what i've done is adding a custom content security policies to make our application work with some of the libraries that we are going to work on soon okay so inside the body we can see that we are linking to this script which is the main dot sx which is just right here so this main dot sx is basically where we are going to run our react application yeah actually we can like start removing some unnecessary file like inside the asset folder we can remove these icons okay inside the index dot css we can clear out the the entire content because here we are going to use tailwind for managing the style of our application uh inside the components folder we can remove this uh version components it's we don't need it and now let's open up this app dot sx let's remove this import and actually let's remove all the content inside yeah we can just remove also this and also this uh class name okay now we have a clean installation and we are ready for start working on our application itself okay let's go ahead and let's start by scaffolding our renderer project so let's close this app dot sx tab what we are going to do is having like multiple folder under the source folder of the renderer project where we are gonna store different type of files for example under the components folder we are gonna store all of our react components and let's create an index dot um ts file where we are gonna use this file to export all of our react components from a single entry point file okay next step we'll be creating a nooks folder this looks folder will be used to collect all of our uh custom hook then we're gonna have a store folder we are gonna use jotai as our state manager and inside the store folder we gonna collect all of the atoms that jotai used for managing his internal state and finally we also have another folder called utils where we are gonna store some utility function that we are gonna use throughout this project okay that was it for our renderer project we have to create also another uh fold called chart that will contain uh like configuration file function types that are going to be shared amongst these three uh folder right here so main preload and renderer great that was it for our project's unfolding what we have to do now is update our typescript configuration and in order to add some path aliases so let's start by editing our tsconfig.web.json this typescript configuration is mainly used by our renderer process and preload uh process so let's go ahead and under the include uh field let's add a new entry which will be source chart and then we are gonna set this uh pattern and basically here what we are doing is including all the folders and subsequent uh files that we are gonna place under the share folder so that basically uh the renderer and preload will have access to this file uh that we have the declared inside the share folder okay next step will be adding some properties under compiler option for example we need to add the unused locals and set it to false under the paths field we are gonna add some uh some new path aliases will be these two which i'm going to paste it here so we basically had the uh shared path aliases and also the head slash uh path aliases so every time in an import we um import from add slash and something else we actually meaning that we want to import from this folder right here which is source render source so basically we are uh saying that we want to import from under this path right here so we have to do the exact same thing for the tsconfig node.json which is a typescript configuration that this time is not used by the renderer and preload but is used by the main uh process great so so let's go here and we have to do the exact same thing we did for the tsconfig.web.json um actually let me just format this document with json uh great so we include the source charret to include every file we declare inside the charret folder and what we have to have is adding a base uh url and set into the uh root of this project and also add some uh path aliases so in this case we are saying every time we start an import with add forward slash we are actually importing from the source main so from here and also the path aliases form importing files from the charret uh folder so that was it for the typescript configuration and now we can go ahead with the development actually we are missing another sub folder under the main process which we are gonna call lib and this lib folder basically will contain a series of function that we are gonna use as an an api to interact with the file system to save read and write some files that will represent our uh notes that our application will display and interact with great so now that we have all the folders that we need we have to update the electron vit configuration so this file basically contains a vit configuration for every process that electron use under the hood so a vit configuration for the main process another one for the preload script and finally another one for the renderer process so here we have to specify the how to resolve the path aliases that we uh basically defined inside the typescript configuration so let's go ahead and let's define our resolve property where we are gonna specify the aliases so here we are gonna specify for example for the main process the lib folder that we have just created and this will be uh resolved under the source main lib so this one right here so the main process need also to resolve the head charred folder which will be resolved under the source charred folder so every time that we specify at shard this will be resolved under source uh charred uh great okay so we need to do the exact same thing also for the renderer process but this time i'm going to paste all the configuration that we need so as you can see here we specify the head charred folder one for the hook one for the assets one for the store and basically they are mapped nicely with all the folder that we define just like here actually we are missing another configuration which is called assets include where we basically want to import all the assets from source render assets and defining all the possible folders and files this will permit us to import you know all the files that we define inside here as an asset and not as a source code uh great that was it for the electron weight configuration and now we can move on and starting by adding tailwind css to our project and let's install tailwind css we need to run yarn and dash d tailwind css post css and auto prefixer next we are going to run mpx tailwind css init dash b which will create the tailwind config.js and the post css config.js great now let's open up the tailwind configuration file inside the content property we have to specify what file tailwind have to look out where we are going to use tailwind so we can just paste this part right here source render and all the file that has this extension right here and you can see that we are basically telling tailwind to go under the renderer folder and search for tailwind classes right here because the renderer folder it will be the only place where our application will live and so all the styling will live uh only here uh great next step will be updating the index.css so inside here we have to import all the tailwind directives so the base directive components and utility for making tailwind working properly then we are going to extend the layer base inside here we need to use the id selector for selecting the root id class we are going to apply hfold another configuration will be updating our html and body we are going to apply fold width we are going also to apply select to none this is for avoiding to select text inside our windows because you know um desktop application does not have you know um generally don't have this kind of user experience what we can select text so we are going to uh disable it for uh for our application uh then we are going to apply also transparent background because we want a nice blue red effect uh as a background of our application uh we are going also to apply uh font mono and also an anti aliased text to make the the text looks uh smoother and nicer also we are going also to add text set to the color white and finally we are going to apply overflow hidden to hide all the content that will overflow the window content great great job so uh that was it for our index.css configuration and now i guess that we can uh we need to install some other dependencies so let's clear out the console and let's add for example tailwind uh merge great we also need to install clsx and finally we are gonna add also react icons okay great so let's see why i've just implement this library and let's move inside the utils folder let's define a new file called index called index.s and let's define a function called n let's extract some arguments which will be of type plus value need to import uh this class value from clsx uh great and then we want to uh run tailwind merge with clsx of these arguments basically so like this obviously we need to import this clsx and also we need to import tailwind merge from tailwind merge uh great uh this function will be useful later on when we have merge some custom class name with default tailwind class name and also applying conditional class name as well uh great great job so let's go ahead inside the app.tsx and inside here let's define span element where we are gonna say we're gonna say hello from electron and we are going also to apply some class name like a text of four excel text blue 500 and also we try to center these these tags by using flex h uh full items uh center and justify center yeah let's try to run our desktop application so let's run yarn dev and you can see that it's working properly we have our stream which say hello from electron and we can see that tailwind is working correctly and also our windows is rendered correctly so I think that our step will be adding some window customization but for doing that we have to go inside the main slash index dot yes okay let's go ahead and let's start to adding some custom styling to our window we want to apply the center that to true to have like our window to be centered when we open up for the first time we want the title to be a note mark which is the name of our note taking application we want to disable the frame vibrance is set to under window uh this is needed for applying a background blur effect and also we are going to apply a visual effect state set to active we are gonna set the title bar style set to hidden so this property is for basically hiding the out of the box top bar that every windows comes out with and also we want to adjust our traffic light position and we are gonna set the x value set to 15 and the y value set to 10 great and that was it for our styling now if we try to rerun our application you can see that we have a nice blurred background actually here is not super visible so let me just move it in a place that could be more visible like inside a second desktop and you can see that the windows had this nice blur effect which will for me it's super cool to have so we can also see that the top bar has been hidden and the traffic light has been adjusted a bit but we don't actually have a way to move our window around but we will see how we can be able to drag our window around let's move into the renderer project and let's start by defining an app layout .ya6 component the first component that we have to define is the sidebar which will be used for containing all the list of our nodes the sidebar will be an aside element and we'll basically have a class name we are gonna have basically we're gonna use tailwind merge where we are going to apply some default styling we'll have a fixed width of 150 pixel we'll have some margin tops set to 10 pixel an height set to 100 percent viewport height plus 10 pixel and finally we'll have an overflow set to auto in order to render a scroll bar when content inside overflow the parent container and we also want to apply some custom class name that we are going to receive from the props so let's define the component props of a an aside element can extract the class name from here great and we can also extract the children in order to be rendered inside this element and finally we want also to spread these props here great this is for you know building a reusable component and we are going to apply this pattern for every component that we are going to define throughout this project great so this is for our sidebar layout component the right part of the sidebar that we are going to call content which will contain basically our note editor so this basically will be a simple div element and we are going to do the exact same thing we did before so we are going to extract the children we're going to extract the class name and all the props and we are going to to spread all the props here and also apply a class name where we are going to use a tailwind merge to merge the full style which will have black set to one to take all the available space except the the one takes from the sidebar and then we are going to use overflow set to auto and also we are going to merge it with the class name that we take from the props we have also to define the children and actually here we have to return this div okay so actually for this content component we want also to have a ref prop and so basically we have our component defined like this let's define also a content display name so basically here what we did is forwarding the reference of an html div element and getting all the props of a div element and then basically going here and passing the the reference this will be very useful this reference prop because we are going to reset the scroll of this content component when we you know navigate through different nodes we need also another component called root layout which will be the container for this sidebar and content component so the root tool layout will be basically a main element we are going to do the exact same pattern we did for the other component so let me just space all the props that we need here let's spread out the props let's define the children inside the main content and then let's define the class name which will use also the tailwind merge we're gonna merge the default style which will be a flex with flex row because the sidebar and content are basically two column layout in a row so we are we are gonna use flex row and we are gonna also take total height of the screen and obviously we are gonna merge this with the class name that we get from the component props great that should be it for our layout component what i'm going to do now exporting everything from the main entry point file we can go ahead inside the hub.tsx instead of rendering this we can just define our root layout with our sideman we're gonna set the texture inside bar as the content of the sidebar and also we are gonna set a class name where we apply some padding and also we are gonna apply some border this border will be red color and this is just temporary just to understand how these two components will be laid out and also we are gonna have our content actually here we can remove this dot and use the hat symbol and it should work let's go back and let's define a class name with some border and border flow 500 great so let's see the result great you can see that we have on the left side our sidebar with these red borders and on the right side our content component with blue borders and here by setting like that margin we have some room to give to these traffic lights so great i think that we we did a pretty good job so far we can go ahead and actually remove this and also this border because obviously we don't need it for our final application and what we are going to do is to add some custom tailwind class for the content where we are going to set the border left and we are gonna give a background of using the zinc color particularly the 900 variants with the opacity set to 50 and also we are gonna set the border left set to a white 50 uh sorry not 50 but 20 you can see that now we have a darker color for our content while our sidebar has this softer gray and and actually more blurred and we also add this border left to separate you know the sidebar from the main content a great great job okay so if we run again our application we can see that our window cannot be dragged around even if i'm clicking on the top side of the window so we need to figure out a way to let the window be draggable when we want to drag it somewhere in our screen so in order to do this we're going to implement a new component which we are gonna call draggable topbar.sx this will be basically another component we are gonna pass some class name so basically we want this header rendered on the top side of our application so if i run again our electron hub we want the header be rendered like here so in order to do this we're going to position this component absolutely and set the inset to zero inset zero means that we set the property top left right and bottom to zero we're gonna give this header an 8 of 8 which should be 32 pixel and we are gonna give our transparent background and we actually can use an autocloss tag here and great so this is our header like our draggable topbar and now we need to import this topbar inside our hub first off i'm going to rename this to become a constant variable i'm going to use this syntax right here to be more compliant to the other components that we are defining and here we're gonna use a fragment and move all this content inside because we want the draggable topbar be rendered outside the root layout so here declare our draggable topbar actually here if we want this to be extracted from the components we have to go inside our index.yes and extract in the draggable topbar here so now we can remove this go here and let's extract the draggable topbar okay actually if we go back to our draggable topbar i want to outline where this topbar would be rendered so we're gonna set border 4 with some border with red color and now if we switch to our application you can see that the draggable is just right here but it's still not draggable in order to make it draggable we need to open up the index.yes and adding some new custom CSS here specifically what we are going to do is to select our header with the tag selector and i'm going to define the webkit up region to drag and this will allow our header component to be dragged around we have to define set the webkit up region to no drag this is super important otherwise our button will not be clickable this is the proper way to allow our window to be dragged correctly so if we switch back to our application now if i hold click on this draggable header you can see that i can move my window around while if i click inside other part of the window obviously the windows cannot be dragged around but only if i click over here great so we have our draggable topbar and we can actually remove the border or now it should look like more clean it's working as we are expecting to the application to work great job next step will be implementing the the two action button for creating a new note and for deleting a note and we'll place this action button rows over here so the first component that we are going to define will be placed under a button folder and inside this button folder i'm going to create a generic action button component this will be will be rendered as a button next step will be extracting all the props of a button so like this props of button right pass these props over here uh great so next we want to extract the class name and the children and pass the build declare the children by the button content and next define the class name we are going to use tellwind merge we're going to define add default style so our button will look something like this we'll have a horizontal padding of two and vertical padding of one we'll have rounded borders then we have some border and the border color will be of zinc 400 with opacity set to 50 then on over we are going to apply a background of color zinc 600 with opacity always set to 50 and we are going to apply a transition colors and the duration set to 100 and also we are going to merge with other possible class name we received from the other props so this will be represented as our action a generic action button so now i'm going to define an index dot yes to export our action button from here and then going inside the root index of our components and export everything like the button folder great job so let's close some tabs okay at this point i'm going to create a new node button dot yes x great this new not button will use our previously defined action button and we can actually import this from components so add slash components great as content we want to display an icon which i have already selected and that we are going to extract from react icons the icon itself is this loofile signature so let's define it here and also let's set the last name to have a weight of 4 h of 4 and the tag set to zinc 300 we want also to extract all the necessary props but actually what we can do here is to inside the action button and define the action button props and reuse this over this type over here and at this point we can do the exact same thing here because the new not button will is basically an action button so we can pass all the props over here and next step will be obviously exporting this new not button great and now let's define the daily not button dot yes x it's basically the same thing we did for our new not button so let's define an action button over here great here we can import from components great next we are going to extract all the props action button props and passing these props spreading these props over here and finally we are going to use this icon at fa rag trash can so let's set as the content of this component and let's add some class name to our width 4 h4 and attacks of zinc uh trian great let's export this component over here hit not button and finally we are going to implement under the component folder a new component called action action buttons row dot yes x which will be the container for these two button it will basically be a div containing these two buttons that we have defined first will be the new not button and the second will be the delete not button uh we can update the import and here we want also to extract all the props from the div from a div element and spread it out here great great job okay now that we have this action button row we can import it inside our app dot yes x so inside the sidebar we are going to remove this sidebar string we are going to define our action button uh row applying this class name let's justify between and a margin top of one and actually we have to export this action button row great so here we can just import from add or slash components let's switch back to our application and there you have it our action button row with the button for creating a new note and the other button for deleting the selected note great job okay great so our next step will be adding a list of notes to display inside our sidebar we're gonna start with some mock data and later we are going to implement a file system based approach to retrieve the notes from directly from the file system but for now we'll start with some mock data okay so to implement this we have to go first inside our shared folder and define a new file called models dot yes which will contain the models of our application we're going to define two specific uh type the first will be a note info which will basically represent the preview of a note such as his title and his last edit time so we want a title and we want also the last edit time this last edit time will be a number since we are gonna use milliseconds to represent our date second type that we are gonna need is note content will be a string but this type over here will represent the markdown stored inside a note great that is for our models and now let's go inside our store and let's define a new folder called mocks and inside this mocks let's define an index dot yes and over here we are going to define some fake notes that we are gonna use let me just paste some notes I have already created so we have basically four notes the first one which is a title of welcome and our last the time of basically now and order three notes and you can see that we imported the note info model directly from the shared folder a great great job okay now let's close all this tab and let's move on inside the components folder and let's define a component called note preview our list dot yes x so this not preview list will be represented by an unordered list html element and for now we don't want to display anything inside so now let's import our notes from flash for flash mocks and over here let's track our notes mock slide our unordered list let's iterate over these notes and let's map it over a basic string return a list item and as a key we are gonna use the title of the notes okay great now let's export this note preview list here great and let's import this component by the app dot yes x and we are going to add this note preview list under the action button row we want to set some class name where we are going to set some marching top and a space between those notes of one and also we have to take an input these props that we define over here so let's define the component props on an unordered list html element let's spread these props over here and we should be good to go so let's switch back to our application and you can see that our notes are here even though they are not displayed nicely so what we are going to do now is extracting a specific component for rendering each of this note with a proper design so what we are going to do is to define the new component called note preview dot yes x okay the first thing that we are going to do here is exporting a type called note preview props which will be represented by the note info that we defined before we are going to also add a property is active to know if the current note is selected as the note to be rendered inside the editor and this will be a boolean and finally we want all the component props of in this case a div element so let's get all the props over here and let's spread out these props inside the div element great so over here we can now extract different props such as the title of the note content of the note the last edit time the is active prop which we by default set to false last name and the other props great so now we are going to define some styling for this if we are gonna use the cn function that we just defined before because we are gonna have some default tailwind style some conditional styling and also we want to merge everything with the other styling that you can receive from the consumer point of view so here let's start by defining our default style we are gonna have the cursor set to a pointer so that every time we get over a note the cursor will be set to a pointer we add some padding that rounded border that the transition colors the duration of the transition to 75 and then we are gonna add some conditional styling for example wave one in the ground we set to add zinc 400 with an opacity of 75 only when this note preview is active so it is the one that is being shown on the right side editor we want to apply also an over effect of bg zinc 500 with an opacity of 75 when this note preview is not active and finally we are gonna merge everything with the with the class name we receive from our parent consumer great job so as the content of this note we are gonna have an h3 which will render the title of the note we're gonna apply some style such as marching bottom on set to bold and we want also to truncate the text if it's too long and then we are gonna hold supply a span element where we are going to render the last edit time and that's some class name to have you render as an inline block all width margin bottom set to text set to press small font set to light and text set to left great great job and now we can start using this note preview but before we need to export it dot slash note preview great so now if we add back to our note preview list here inside of rendering this list item we can use our note preview and passing basically everything it's so here we can do just like this and we have to specify also okay we're gonna use the note title plus the note last edit time and let's take a look at the result so as you can see now we have our note and every time I over over a note we get this nice transition effect the thing that I don't like right now is how this last edit time is being formatted we want this to be formatted in a proper way so to achieve this let's go ahead and let's go inside the util folder and open up this index dot yes and over here we are going to define a date or matter which will be equal to ink date time format and here we have to specify the locale for now we are going to use n dash us and define some option for example the date style we are going to set short or the time style we are going to set short as well and for the time zone we are going to use utc and finally we have to export a function called format date from ms which will take number of millisecond and we are going to return we're going to use this date format and format this input we takes as input a great great job so now we can go back to our note preview and let's define a variable called date and here we are going to use the previously defined function for mat date from milliseconds we are going to pass this last edit time and this date now will be a string that we can render over here so now if we go back to our application you can see that our date is formatted nicely we have the date rendered in this format day months and here and the time of the day basically a great great job the last step is that if we go back inside the utl folder we want this to be based on the locale of the operating system so in order to retrieve this type of information we need to ask help for the preload script and so if we go over here what we can do inside the context define a variable called locale which will be a string and if we go in the index dot yes over here we are going to define this locale will be equal to the navigator dot language so the navigator is an api that will return back on default information about the user agent in this case the underlying operating system and the language will return us the locale of the underlying environment so we can start using this kind of information so let's head back to our date for matter and here what we can do is the window dot context dot locale don't worry about the server because type script actually is complaining about this type not existing but i can assure you that this variable exists it's only type script that is complaining we can just don't worry about it so yeah let's move on and let's close this and now we have our note preview formatted based on the underlying locale great great job okay next step will be going back to our note preview list and we want to handle a particular use case when we receive like we don't know if this note smock can be empty or not and in case it is empty we want to display some kind of content like no notes yet so in order to do this let's define an if statement we are gonna say if not smock dot length is equal to zero we are going to return this component over here we are gonna have a span which will say no notes yet and we're gonna apply some styling using tail width merge merge the fourth class name we are gonna extract over here and also a default style which will be equal to text center and apply the top of four and also spreading out all the props great so now if we go to our notes mock let's remove this for a second let's save it now our notes mock is an empty array so let's see what happened you can see that no notes yet string here great so now we can move back to our notes mock and restore our initial note and let's see the result now we have our notes great okay finally since we have extracted the class name over here we have to pass it here so if we open up our note application you can see that now they are formatted nicely okay now it's time to implement our note editor and for doing that we are gonna first extract a new component which we are gonna call markdown editor dot sx great so then importing the component here at this point we need to figure out a way to implement this editor i decided to use a third-party library which is called mdx editor which is basically a markdown editor built for react as a component which allows us to write markdown documents just like google docs or notion does there is also a live demo we can try it here for example if i go here and the characters for having an adding you can see that we have already previewed inside our editor for example if i want a block quote here a block quote everything is styled directly in the editor great so now we need to figure out how to install this component inside our uh notes application so let's go here and open up a terminal i will open up actually a second terminal for installing this and we are gonna run yarn and add mdx editor forward slash editor great our editor has been installed now we need also another package which is called tailwind css typography let's go ahead and install at tailwind css slash typography great now let's open up the tailwind config because we need to specify the plugin hover here and just add this statement like required tailwind css typography we should be good to go now let's go back to our markdown editor and here let's start using our new editor we have just installed so we're gonna say mdx editor like this and we need to add some required props like the markdown and we can say for example hello from mdx editor okay now let's go back to our app.sx and let's put our editor inside the content container so let's remove this string and let's say markdown editor like this great let's see our application and you can see that anything is rendered and that's because we need to apply some custom styling so let's head back to our markdown editor and let's add a prop called content edible class name and this is a way to apply some tailwind class name inside our mdx editor first thing we want to remove any possible outline then we are gonna set min age to screen to give like a minimum height of the entire window screen then we are gonna set max width to none we want that every time we stretch our windows the content to grow consistently with our window then we apply a text large px of 8 and py of 5 and also we're going to apply a color yellow of 500 to modify the blinking color okay let's say and let's see if anything has changed okay you can see over here we have this yellow color and we can start typing something and see that we have our editor working properly so the problem now is that the markdown is not supported like if i try for example to add an adding like this is not converted this is for two reasons first we need to define some plugin over here because this editor works by specifying multiple plugin the reason behind having plugin is that in this way we can select what plugin we want to bring in in order to avoid a very large bundle size so we only gonna import four different plugins the first will be the headings plugin to format the headings properly then we are gonna have a list plugin to format like number at least or bullet list and we're gonna use quote plugin or block quote and finally the markdown shortcut plugin that will allows us to define some markdown shortcut to define some of the markdown syntax the next step will be applying some rows to our editor this pros will come directly from the tailwind css typography that we previously installed we're going to apply this pros and also pros invert to have a dark style of this pros styling that tailwind is going to apply and then we will have a lot of different content that i'm going to pass here because it's too long and basically what we had here is basically some styling for styling like the for example here we're styling the paragraph since we are targeting the p tag here we are styling the whole the headings to have a certain margin here we're styling the block quote here they are not the right list and so on so now if we go back to our application you can see that everything is working as expected our previous content that was saying hello from ndx editor and was on h1 is being correctly rendered so if i go here for example i want this text to be bold i can select this text for example and with command b make it bold or otherwise i could have used the markdown index for the bold text like this one oh actually i have to do like that text bold and you can see that the markdown shortcut is working probably we are also for example the palette list like one two and three and also i guess the numbered list one two and three yeah you can play around with this editor and edit your note content but the most important thing to remember is that for this application we are only adding this plugin and we are intentionally letting out some other plugin like the link plugin or the code block plugin because they need a particular configuration that maybe i will leave it for another video but for now we are good to go and we have our editor working fine great okay now let's open up back our application and i want to show you something if i force this content to go overflow you can see that our scroll bar are not looking so nice so we have to apply some styling to modify the appearance of this right scroll bar and for doing that we are gonna head inside the index.css and here we are gonna add some new css the first class is dash webkit dash scroll bar where we are gonna apply a width of two to modify the width of our scroll bar then we are going to modify how the thumbs will look like so colon colon dash webkit scroll bar and then we are going to apply a background of dash 555 and round that to md great and finally we are going to modify the scroll bar track and we are going to apply a background transparent background great so if we go back to our application you can see that now our scroll bar looks very very nice great that was it for the scroll bar so let's go ahead our last UI component will be called floating not title.css which will basically be a floating text that will appear on top of the editor that will in task of what note we are editing right now so let's go ahead so we are gonna use a div parent container move the content inside we are gonna use a mock title for now it will be note title and we're gonna use this title type the span let's apply some styling like text gray 400 and then let's apply some styling as well to the parent div container where we are going to apply a tailwind merge of black styling and justify center also parent class name and we also need parent props so let's go ahead and extract all these props from component props of a div element from here let's extract the class name great let's export this file export everything from floating not title and now let's go back to our app.sx and let's define this component just over here floating not title while applying a class name on padding top and great let's go back to our application and you can see that now we have our floating title okay now that our application is actually complete we have all the components that we need we can start move on on implementing the interactivity for this note application basically we want that every time we click a note the content of that notes is displayed inside the editor we want the note preview being rendered as active and so on so the way that we are going to implement all the interactivity for this application is by using jotai so let's go ahead and install jotai let's clear out the console and run yarn add jotai okay so jotai is a simple and powerful react state manager that will permit us to share some global state throughout our react components and also we will help us to avoid re-renderings and help us also to having a better separation of concern as well as having a better code readability so if you remember from before we have this folder store where we are gonna have all the atom that jotai used to managing is state so let's go ahead and let's create a new file called index.ts and inside this file we are gonna have all the atoms to store our application state so let's start by defining an atom to store all of our notes so let's define constant variable called notes atom which will be atom of note info actually will be an array we actually need to import atom from jotai and the initial value for these notes will be given from the notes mock okay let's go here and let's import this as slash four slash mocks so for now we are importing some the mocked notes but later we are gonna load the notes directly from the file system but for now for the sake of the tutorial of this video let's start with some fake data okay so this atom will be useful for retrieving all the loaded notes the second atom that we need is called the selected note index atom which will represent the index of the selected notes and actually this can be a number or it can be null in case we don't have any notes selected for example when we don't have any notes yet great so we will have also a third atom called selected note atom will be a read-only atom so we are gonna extract the get function and what we are going to do is getting our notes by getting the notes atom then taking the selected note index which will be equal to the selected note index atom if the the selected note index will be null we are going to retire null as well so from here we can extract the selected notes which will be retrieved by accessing the array of notes with the selected note index and from here we are going to return all the info related to the selected note along with some content which for now will be something like hello from note and we are gonna use selected note index great so if we are gonna take a look at the type inferred by type script we are gonna get back the title last edit time and the content or if we have any selected note we will get back null great now that we have defined all of these items what we can do is start defining some books to manage the core business logic of the entire note application so let's set inside the folder hook and let's create a new file called use notes list dot sx so here we are going to define an hook that will return the list of notes that we basically retrieve from the jotai internal state so let's export use notes list okay from here what we are going to do is first define a variable called notes which will represent the notes that we have defined so we are going to use use atom value of notes atom so here what we can do use the patalis st we defined before so these notes now are gonna be a list of notes and this use atom value is for reading the value of an item without being interested on the set state function we also need the selected note index and also the set selected notes index that we are gonna get from the use atom of selected note index atom so here we're gonna have the currently selected note index and this will be a function to update the selected note index uh great now let's define a function called handle notes select which will take an input an index is a number it will retire a function which will be actually an async function this is an handler that we are gonna use every time we click on a particular notes so every time I click on a note I want that index to be updated accordingly so let's go here and say set select the note index to the new index actually we also want a new parameter which say on select which will be actually a function an optional function and this on select if it will be defined will be run after the set select note index will be executed so at this point we need to uh return some value uh actually here we're not equal great so here what we are going to retire are the list of notes the selected note index and also the handle not set up uh great we're gonna use this hook inside the note preview list where previously we were leveraging the notes mark but now what we can use is this atom we have just defined so let's go here and define use notes list okay let's go here and update our imports also here we can components and remove this uh great so now we are going to extract the notes select the note index and instead of using notes mark we are going to replace this with notes great also we are going to define some new props for example is active which will be equal to the selected note index is equal to the to the note actually here we have to extract the index of the current note so here we're saying this note preview will be active if the selected note index match the uh like the index of these uh notes basically and finally we are going to define ununclick handler where we are gonna execute the handle not select by passing the index great so now if we go back to our application you can see that i'm able to select a note and every time i select a note it goes basically in an active state by applying this color to in that that note is selected even though the content is not getting updated but as you can see everything is working as expected and also the notes are displayed correctly okay the next step will be updating the floating note title every time we change the selected note and also the content of the note itself so let's close this tab and let's get back to floating note title and here instead of using a fixed string what we are gonna use is the use atom value of the selected note atom and here we are gonna have the selected note but this note actually let's remember that can be undefined so in case it is not defined we are going to retire now and if it's defined we are going to return the title of the note great so if we go back to our application you can see that now the title of the floating note title is updating accordingly so if i select the note too the floating title is updating correctly but actually if i click on the first note something is wrong because our floating note title is not appearing and that is because i think we have as a little bug inside our store index file here we are doing something incorrectly so we are saying if not selected note index but but here instead of say not selected on index we have to be more specific and say that if this selected note index is null or undefined then we will return null otherwise that operator will return you know false even though the selected note index is zero so if we go back to our application now it's working correctly great okay as a next step we'll be updating the content inside the editor to match the real content of the selected note so let's go ahead and inside the hooks folder let's define a new hook called use markdown editor dot isx inside this hook we are going to take the current selected note by defining use atom value of the selected note atom and basically for now we are going to return the selected note so now this hook can be used inside our component markdown editor so we can go here and use the use markdown editor and extract the selected note but let's remember that the selected note can be null so in case this selected note is null or undefined we are going to return null so we are not going to display anything great the next thing that we need to update is the markdown content which will be equal to the selected note content and also we have to update the key to selected note title so here by setting this key we are saying that every time we have a new note this editor has to be re-rendered from scratch so the entire content will be updated correctly with the expected content inside great so let's say and let's open up our application and see if everything is working as expected we have note two selected and it's a hello from note two if i select the note one say hello from note one here we have hello from note zero because this is actually the note with index zero but you can see that the content is displayed correctly and actually you can go here and type something even some markdown like an adding or a block quote or you know some old text but obviously as soon as we change note and we go back to the previous note all the content will be lost forever because we don't have any way to save our content soon we will see how to save the content of the notes inside our file system in order to be retrieved later on and don't lost our changes great okay before moving in i want to show you a quick thing that maybe you didn't notice so if we go back to our application you can see that if i scroll the content and now i open up a new note you can see that the page like the note content editor gets scrolled so it's very important to reset the scroll every time we change note so in order to do that what we need to do is the following go back to the app.sx and here we are going to define content container ref which will be defined to be a use ref as an html div element which by default will be null we need to import the use ref hook and now obtaining the reference to this content by saying ref equals to content container ref great so at this point what we are going to do is to define a function called reset scroll where basically what we are going to do is to take the content container ref take the current value and if it's defined we're going to set the scroll to zero zero or it means we scroll to the top and this reset scroll needs to be passed to the not preview list so we need to execute this function every time we select a new note but who is in charge of selecting a new note is the not preview list so what we can do is defining a not select prop here where we pass this reset scroll function and now let's go inside the not preview list we need to extract this on select from the component prop so let's go here and let's define an export type not preview list props which will get all the props of a ul list html component but we will get also this on select now we can replace this with the not preview list props and here extracting the on select and this on select if you remember from before can be passed to this use not list hook which will execute this on select after we select a new note obviously if it's defined so now if we go back to our application you can see that if I scroll a little bit and then change not you can see how the scroll is set back to the top let's do it another time change not and we get back to the original scroll great great job now let's move on and let's open up our application what we are going to do now is add in the interactivity for creating a new note and the relative function for deleting a note great so let's close some of this tab and now let's head into store index dot yes and here we are going to create two new item one for create and one for deleting a note so let's start with the creation of a note so let's call it create empty note item and this will be actually a writable item so the first parameter will be now then we are going to extract the get and set function and we are going to execute our function so first thing that we have to do is getting the list of our notes by saying gets notes item so we need to read the list of our notes and from here we have to define a title for our note so for now we can use a note and using basically a sort of index by calculating how many notes we have until now and then summing one great then we are going to create our new note to be able to note info and here we need to pass title and also a last edit time we can use date dot now to get the milliseconds of the current time where the note has been created great now we have to update the notes atom to add a newly created note so let's say set notes item and we want a new note to appear on top of the other so we are going to say new note and actually what we want to do here is to spread all the previous notes apply a filter here what we have to do is get the current note and say that if the note title and we want only the notes that are different from the new note title so in this way we are keeping only the notes that doesn't have the same name of the newly added note so this one is for updating our list of notes another thing that we want to do is update the selected note index item and set it to zero the reason why we are using zero is because our new note will be at index position zero and generally when we create a new note we want that to be selected so in this way we know that it will be at index number zero and with this trick we are going to select that new note great job so that was it for our create empty note item we are gonna have also a relative function for deleting a note so delete note atom will be also a writable atom we need to extract get and set and we need also to define this function so here we have to get our notes so we are going to get the notes right then we get the selected note by saying get selected note atom obviously when we say delete note we want to delete the note that is currently selected great here we need to check that if you don't have any selected note we can just return because there are any notes who delete and then what we are going to do to update the list of notes so the not set them and say that apply this filter where we say not title is not equal to selected note title so here we are saying let's filter out only the notes that have the same name of the selected note basically and after having removed our selected note we don't want any note to be selected anymore so what we are going to do is to set the selected note index atom and set it to now a great great job okay now the next step is adding those handler to the relative button so let's go inside the new note button and over here let's take the create empty note which will be retrieved by using the use set atom of the create empty not atom or the atom that we have just defined before then we are going to define a function called handle creation which is a function that basically will call this create empty note and we are gonna pass this handler to the unclick event handle creation great job we are gonna do the exact same thing for delete note button so let's go here and say const delete note which will be equal to use set atom of delete note item we have to import this actually here we can remove the renderer right and then we are going to define our handle delete which will be a function which will execute the delete note are great so finally you will pass the unclick event handler to execute the handle delete great job so let's test these changes now let's go back to our application so now if I click over this button a new note is created even though the content is not you know correct but you can see that now the notes are correctly added you can add as many notes as we want if I hit the delete button the notes is selected and we don't get any notes selected anymore so we can delete this not eight not seven not six and not five great great job as of now our application is almost ready but it means the most important part which is storing our notes inside the file system in order to persist the data even when the app get closed or quitted unintentionally and so on so the current implementation that we have is inside the store index dot yes where we are using jotai to store basically our notes but currently we are using fake data so what we actually are going to do is to persist our notes in the file system in particular if I open up the terminal we are gonna store our notes inside a folder called note mark which I have already created for now and this folder is going to be declared under the user home directory in my case is inside user forest less jonata forest less note mark we are gonna have multiple note like for example note one and we are going to use the dot md extension in order to highlight the fact that this file is a markdown file great so the point is now understanding how we are going to interact with the with the file system so basically we are gonna use the node js file system api which provides a multiple function to read file or you know get in file information writing file but the main important thing to understand is that since this is our restricted api which can be accessed only inside a node js environment with privileged rights all this kind of operation will be executed inside the main process in particular if you recall from before we have our main folder and under this main folder we are going to define under the leap folder a set of function to write read and you know getting notes information inside this note mark folder that we define to store our notes what we are going to do now is first defining this function to interact with the file system and then inside the preload script we are going to define some ipc function that will permits the render process to communicate with the main process to perform this kind of operation let's go back to our application and let's close these tabs now we are going we are going to work inside this leap folder and i'm going to create a file called index.ts inside this file we are going to be in a node js environment and we are going to define all the function to read and write this uh notes file so the first thing that we have to do is installing a third-party dependencies called fs extra so yard add fs extra so this library is a library which is built on top the node file system library but it is enunciated with some utility function and also it is promise based so it will help us having a nicer experience working with the file system great actually we need also another library called load ash so let's install it you are not the load ash and this library will provide some utility function for example throttling and the bouncing function or some javascript based utility function uh great so here i'm going to start to define a first function that is going to be called get root directory or there and basically what this function will return will be a string that will return the root directory of our node application and as you might recall this will be placed under our home directory under the folder node mark great as you can see here i use the function omdir which return the string path of the current users om directory and it comes from the module os which can only be accessed from an ojs environment also here i don't like to have this artcoded string so under the charred folder i'm going to create a new file called constants dot ts and i'm going to export some constant variable like up directory name which is gonna be called node mark and we're also gonna have the variable file encoding which will be equal to utf 8 actually here i made a typo encoding uh great so now this variable can be reused and will help us avoid you know having code duplication and artcoded string uh and so on uh okay let's cross this tab and here now we can use up directory name which will come from the charred library under the constant file the second function that we are going to define will be the function get nodes that will be on a sync function that will basically read all the file with dot md extension inside our directory node mark so what we are going to do is first extract our root directory by calling get root dir great so now we have to uh check that the directory node mark actually exists and if it doesn't exist we need to create it and for doing that this kind of operation there is a function called ensure dir that comes from the module fs extra which will do this kind of operation for us great at this point we're sure that this directory exists and we need to read all the file that are stored inside this directory so let's define this variable nodes file names and here we are gonna use read directory that comes from fs extra we will pass the root directory and also some option like the encoding where we are gonna use file encoding that will come from the charred constants and also we are gonna specify with file types set to false great so now basically inside this variable we are gonna have a list of string representing all the file names stored inside the root directory the next step will be filtering out these notes file names to include only the files that have the md extension so all the other files will not be treated as nodes so let's define a variable called nodes let's get these notes file names and let's filter it out with taking the file name and ensuring that the file name ends with the dot md extension so now basically we have filtered all the kind of files that doesn't have the dot md extension great okay at this point we can be tended to return these nodes file names but actually what I'm going to do is extract another function called get node info from file name which is gonna take a file name they're gonna be a string this will be on a sync function and we return a promise with a node info inside okay so basically what this function will do is taking a file name and basically mapping to a node info type so here what we need to do is basically calling the function start that comes from fs extra and this function takes an input file it will return some statistics about this file so here we go under the root directory and select this file name and the result will be stored inside file dot great and so now we have to return node info as expected from the return type so we need to return a title which is gonna be basically the file name without the dot md extension so for doing that we're gonna use this regular expression that will remove the dot md extension from the file name and then we also have to return the last edit time which is gonna be the file starts dot m time so this property basically return the last edit times in milliseconds that the file was added great so now we have our get node info from file name and we can call a function to map every nodes to the respective node info class so in order to do that we are gonna call promise.hole and we are gonna map with every nodes and call the function get info from file name great so what we did is execute this get node info from file name for every nodes that we have stored inside here and now if we are going to see the return type of our get nodes you can see that it returns a promise with a list of nodes info great okay actually let's copy this type because under the shared folder i'm going to create a new file called types dot ts and inside this file i'm going to export the type of the function we just defined so get nodes and it will be equal to the type of the function that we actually have defined we need to import the model and now this get nodes can be set as a type get nodes as a type declaration the reason why we did this is because this function type is gonna be used in multiple files and so we need to have a shared type great the next step will be going inside the main folder under the main index file this is basically the file where we are creating the window and setting up all the listener and events and we scroll down and we go under the up dot when ready and above the create window statement we are going to access the EPC main which basically is the channel where the main process will be listening for this kind of events we are going to use the function handle to listen for the events get nodes and every time basically we are gonna receive this event get nodes we're going to execute the function get nodes that we previously defined actually let's update this import to be head forward slash leap to leverage the path analysis and if we go back here the first parameter that this an EPC error expect is an event but we are not interested in the event of this function we want all the arguments that can be passed through this IPC channel and we can take these arguments like using the spreading syntax and here we can use a type script trick to get the parameters from the get nodes type that we previously defined and then spreading out these arguments just like here so this is super useful trick so that every time we change this type type script will you know it will act as a reminder that we need to update these arguments right here and also the arguments that this function expects I think that this is a super useful trick even it's a little bit advanced but it will come in and later great so now we set up a listener from the from the main process from the get nodes but obviously there must be someone that will invoke this get nodes and the one who will be in charge of invoking this event will be the preload script so let's go under the index.js of the folder preload and below the locale definition we are going to define the get nodes function which will take all the arguments that the function get nodes expect in input get nodes and here we are going to use the epc renderer.invoke function to call you know the function where the epc main is listed passing all the arguments that it expects so here we have to import the epc renderer from electron and here we have to add a comma so now what we basically did is exposing using the context bridge exposing main word under the context object this function get nodes that basically takes all the parameters of get nodes and when it gets called will basically invoke the function get nodes with all the arguments that this function expects this is basically a sort of proxy to avoid exposing the entire epc renderer object to the renderer process and basically we are gonna do this trick over and over again to basically expose all the pi that this application is gonna use to interact with the file system uh great so the final part will be go inside the index.t.ts and define this newly added function which will basically will be of type get nodes so basically in this way we should get the type inference when we do window.contest.get nodes great great job so at this point we can close all this tab we can go back to our renderer source store index so here now instead of loading this nodes mock we are going to define a new function which is gonna be called context load node this function will be an async function since we are using our promised based API what we can do here is loading all the nodes store in the file system by calling wait window.context.get nodes great and now inside the nodes we should have all the list of nodes info basically by doing all the steps that we are just seeing another thing that we want to do with this function when we read them from the file system is to sort them by most recently added so we are going to return these nodes by using the sort function which is gonna take a and b which are the two nodes that are gonna be compared and we are going to do b dot last edit time minus a dot last edit time so this is a way to compare two nodes and getting the one that is most recent great okay but now we have to call this function load nodes inside our nodes atom because we don't want to use the nodes mock anymore so what we are going to do is actually define another atom called nodes atom async which is gonna be an atom we type node info an array of node info or a promise of node info and this atom is gonna have as his initial value the result of load nodes so the result of this function at this point what we want to do is to use these nodes atom async inside the original nodes atom but here we are mixing some async stuff with sync stuff a way that jotai allow us to handling async stuff is by using the unwrap function so let's remove this item right here and let's use unwrap is gonna come from import unwrap from jotai utils and this unwrap takes an input on async atom in this case not atom async basically the way that unwrap work is that if this promise is resolved will return his original value so the value that is stored inside these load nodes but when it's currently pending what we can do is take in the previous value and just return it so anytime that we call this atom async if it's gonna be pending because it's reading from the file system we can return just the previous value which can be a list of node info or undefined obviously this undefined must be handled by all the react components that are using these nodes ato in fact you can see that now some of the atoms that are reading from the nodes atom start complaining because it say that nodes now is possibly undefined so here what we can do is saying that if the nodes are not defined for example we just retire null as the selected node also here we can say if nodes are not defined just return and also here if the nodes are not defined just return so great we have fixed all of the other that were where typescript were like complaining that something could be wrong if we didn't handle it correctly but now if we open up the terminal and run this comment yarn type check web this comment basically parts all the files and searching for possible typescript error in fact it say that some files are throwing some error like the node preview list.sx let's see what is actually going on and you can see that nodes here is possibly undefined so here as the other case we are going to retire null if the nodes are not defined and also here instead of doing this row comparison what we can do is leveraging the function is empty passing the nodes and is empty will come from low-dash import is empty from low-dash which is much more cleaner so let's run again our type check so on the renderer process we don't have any problem but we have to run it also for the main process we don't have any sort of problem great so now what I'm going to do is to close our application and start it from the beginning to see what is going to happen and you can see how we don't have any nodes because if I open up my terminal if I run dwd I'm under the notemark folder and if I run ls we don't have any file so let's close this application and for example let's create a file node1.md and node2.md and let's put this content hello from node1 we are going to redirect this content inside node1.md and we are doing the same thing for the node2 okay so now inside the folder notemark we have two files if we're gonna bring the content of the node1 for example we have this content great so let's go back to our terminal and let's run our application from the beginning and you can see how our nodes are magically appearing and are directly read from the file system obviously if I'm going to select this file you can see that the content is actually not correct because for now we are just reading the like the file names and some information like for example the date that this file was last edited and now the next step will be fetching the content of these nodes to be rendered inside the editor so let's do it great at this point let's go back to our main lib and here we are going to define new function called read node which will be an async function which will take in input a file name which is gonna be a string and from here you need to extract the as always the root directory and then return basically the content of this node the way we are going to do this is by using the function read file from fs extra and we are going to pass root directory forward slash file name along with the extension that we expect which is dot tmd and then we are gonna pass the encoding to be the file encoding great so now we need to reapply the same pattern we did before so let's open up our explorer let's go inside the main index file and here let's define the epc main dot handle the event read node basically what we want to do here is like secure the function read node and obviously here we need to take all the arguments of the function which will be file name as a as a string here we need to spread the argument and pass it here actually here we cannot do this because we actually need to define the type of our function so let's go inside the sort charred types and let's define a new type called read node will take in the title of the nodes which will be of type node info of property title and basically we return a promise with inside the node content which is basically a string so now this read node type can be used both here need to import it and also can be used as type here inside read node and you can see that now even if we remove this type declaration we can see that this file name is still being third as a as a string great so we can close this we have our handle for the epc main we can close this tab and at this point we can go back to our preload script and define the epc invoke function so here we are going to define the read nodes which is going to take all the arguments of the parameters of read node and is going to return pc render dot invoke the event read node and passing all the expected arguments need a comma here so now basically here we as we were doing in get nodes we are exposing the function read nodes that basically will invoke the event read node by using the epc renderer channel we have also to update the interface so read node will be equal to read node great okay now let's go back to our store index file and we need to use this read node function that we actually define inside the deselected node atom because now we are using his content the hello from nodes which is basically some fake content so what we are going to do here is basically define a new variable called node content which is going to be an await actually here we had to use the async declaration in order to use await so here we are going to await window dot context dot read node and passing the selected node dot title great and now we can remove this fake content and passing the node content as the real content of the of the nodes okay actually we have a little problem now here because this selected node atom is actually returning a promise so we want to extract the synchronous atom just like we did for the nodes atom so we just basically define first a node atom async and then use the unwrap function from jotayutils to extract the the synchronous nodes atom so what we can do is to remove the export from here and make this the nodes atom async and then define a new atom which is going to be selected node atom which is going to use the unwrap function to the selected node atom async and here when this async atom will be pending because it will be for example fetching the content from the file system what we want to return is or the previous content but the problem is that the previous content can be null or undefined and in case it is null or undefined we can return empty node which is going to have an empty title an empty content and as last edit time we're going to set date dot now great so this selected node atom now is going to hold a synchronous value so we don't need to use async await because we just use the unwrap function and we just get back a proper value and we just return back a node or a null value in case we didn't select any node so now if everything went well we should be able to read the content from our nodes so let's run yarn dev and let's launch our application so let's try to select node one but everything seems wrong so when this kind of error happen can open the dev tool and just trying to figure out what the problem is and here we are getting window.context.read node is not a function okay let's see why I think that inside that index.d.s under the preload folder we can have missing something but everything seems correct I think that something is missing here and this is just the error so basically we have an s here and we basically add a typo so let's run again our application let's try to select a node and it seems to work let's select node two and the content is actually correct because we get hello from node two and here hello from node one and also the floating title is updating correctly to check if it's really working what we are going to do is open up our terminal and let's try to put some kind of other content inside our node for example I'm going to use an adding tool which I'm going to say hello from node one and I'm going to set this content inside the node one.md and I'm going to do the same thing for the node two but using an adding three here with three hash symbols great let's minimize the terminal and let's relaunch our application okay let's try to select node one and you can see that we get hello from node one with an adding two and hello from node two with an adding three so we are actually reading the content of the nodes correctly and we obviously can still add our content inside making bold and whatsoever but the problem is that this content is not persisted on these files so our next step will be saving the changes that we made in our nodes in the file system but actually we are going to implement this by implementing an autosaving system or we're gonna save this file when the a node gets deselected so let's do it okay the first thing that we have to do is to go inside our shared folder open up the types and define a new function the function that we are going to define will be write write node and is going to take a title which will be a type node info of property title and is going to take also the content that we are going to save inside this node which will be of type node content and we are going to return a promise with basically void because we are not returning back any kind of value to the who is gonna call this function next step will be open up mainlib index.js and define a new function called export const write node which will be of type write node and this will be an async function that is going to take my name and the content we are going to extract the root directory of our application great i'm also going to console log some useful information in this case i'm going to log writing node with the file name of the node that we are going to write so here we're going to execute write file from fs extra we have to pass the path of the file we want to write which will will be located under root here for slash file name dot md we're going to pass the content we want to write and also specifying some option like the encoding which will be equal to the file encoding so that's it for our function write node this is everything we have to do for you know saving some content on a file in the file system the next step that we are going to do is to define the epc main handler so let's go here and define epc main handle and we are gonna call this write node and this will be a function that will take some arguments we're gonna take from the parameters of write node and we are going to execute the function write node from our library passing all the arguments that this function uh expect okay now let's head to the reload so we are going to define our write node also here and i guess that we can leveraging it up co-pilot and then let's go inside the index dot t dot yes and let's define our write node function which will be of type write a node great okay now let's close this tab okay now let's head into our store slash index and here we're going to define a new atom called uh save node atom which will basically be a writable atom so here we are gonna use the async function where we are gonna we are going to extract the get and set function and also we are gonna need the content which are we are gonna call new content which will be of type not content and this function is going to execute okay here we need to import the not content and here we need to extract all the notes so let's get the notes atom then let's get the selected uh node so here if we don't have any selected node or the notes are not defined as always we retire because we can perform any action so now we can basically save this data on this so let's call window dot context dot write node and here we have we have to pass the title and the content so select the not dot title and the content will be the new content great also what we want to do is update the saved uh nodes uh last edit time in order to show the date of the nodes uh being updated every time it gets saved so here we are gonna use the set function to update the notes atom and basically here we are gonna we are going to use the map function here we are going to extract the current node and here what we are going to check is if the notes dot title is equal to the selected node title what we want to do is to update these nodes by just merging the previous data and updated the last edit time by setting the date and setting the current millisecond and here we are going to return the node so basically this is the node that we want to update so if this is the node that we want to update we update the last edit time otherwise if it is another node we just return it okay so now we can start using this atom save node and we are gonna use it inside the use markdown editor inside this hook we are gonna first extract this action save node by using use set atom and we are going to take the save node atom that we have just defined and this will basically be a function to save our nodes another thing that we need is the editor reference so this editor ref will basically hold the current markdown that is stored as a string inside the the editor so here we are going to use a use ref by passing mdx editor methods and for initially we will pass it on our value let's import this ref from react okay so now we have to define two function the first function will be called handle auto saving basically this function will be triggered every x seconds and will save our nodes automatically so let's start by defining our function first so this will be an async function that will take some content which will be of type node content so here what we need to do is to first check that selected node is valid so if it's not if it's null or undefined we will return otherwise we perform the saving action so let's first add a console log useful for understanding when this function will be triggered so here we are gonna say auto saving and we're gonna also win the selected node title to understand what nodes has been saved and then just call the in the await we're gonna use the save node that we defined here and we just need to pass the content let's go here lx export the editor reference and also the handle auto saving now let's go back to the markdown editor and here let's extract the editor reference so editor reference great so basically this editor reference will be attached by the ref prop and then we have to extract the function handle auto saving which will basically will be triggered every time the content inside the editor will change though here we have the event listener can change the problem with this approach right here is that every time we type something inside our editor we will perform a saving action so a file system operation every time we we type something with our keyboard which can be very very fast so the way to actually better handle this handle auto saving is to throttling the entire function we can go here and extract in the function throttle from low dash and then going here and take this function and put it inside we need also to specify when this throttling must happen so we want the auto saving to for example to be executed every three seconds so we need to pass three thousand milliseconds and we can also pass some option initial auto saving must not be triggered but the last auto saving will always be triggered okay so actually what I don't like here is this this magic value put it here so let's go inside charred folder inside the constant and let's define the new constant value called auto auto saving time that to three thousand millisecond so that we can it can be reused here without having any magic number throughout the code base so what this function will do is to execute the saving operation not every time we press some key in our keyboard or we perform some kind of change inside our editor but it will only be executed every thought second so this will prevent to execute too many operations on the on the file system which can be you know very daunting and affect the performance of the entire operating system and the application itself so now everything should work as expected let's try to run our application and let's take note one for example okay first let's try to insert some content so this is some new content okay so you can see that here we triggered a writing note so actually our content has been saved but for actually being secure of that let's change note and let's go back to our note one and you can see that content has been saved another way to check this is to open up the terminal you can see that here we have our two note in the content of note one and you can see that it has been updated correctly so our saving system is working correctly let's go back to our application because I want to show you something else if we open up the dev tools we have the console here let's just clear it out now you can see that when this auto-saving will be triggered so if I start typing something very fast you can see that the content is not updated on every kickstroke but it's actually executed every every three seconds and this prevents us to as I was saying to avoiding to saving on the file system every time we perform an action on our keyboard great we want to define also another handler because we want to save our notes even when we basically they select a note and we go to to another note because it can happen sometimes that we are typing and we change immediately note and previously note gets not saved because the the auto-saving does not have enough time to be executed so what we can do is going here and define an handler called handle floor which will be an async function which is going to be executed every time we select a new note okay so inside this function what we are going to do is to first if the selected note is not defined we are going to return and then what we are going to do here is that every time we go from a particular note to another one we must be sure that the previously queued auto-saving function must be concealed otherwise we risk to overwrite the content of another note so the way to do this is to go here and take in the handle auto-saving function that we defined here and we run the function cancel so as you can see from the documentation this will throw away any pending invocation of the debunked function so when we deselect and select a note we want also to save the previous content of the of the notes that we were editing before in order to get the content of the previous notes we can leverage the editor reference get the current value and get the markdown content so here we can say that if the content is not null or not undefined we are going to perform the save note operation and passing the content so now we can export this function handle blur at the same time we can go back to our markdown editor and you can see that here we will have unknown blur event so here we can extract the handle blur and execute it here great let's try out our application okay so let's select note one let's say that I want for example to delete some content and put in something else okay you can see that now its notes are not overwritten accidentally and yeah we can put our content our fantastic content inside here without running into any over everything problem now that we can select our notes and edit our notes what we are missing is like implementing a way to create a new note and at the same way implementing the button deletion so we can start with the button creation so let's head into so first let's close all of this tab now let's go inside the types because I'm going to create a new type which I'm going to call create a note which is going to take actually any parameter and we'll return a promise with some note info in particular we are going to return the title of the newly created note but at the same time we want also to return a false because the the process of creating a new note can also be abort and we won't we don't want to return any title in that case but we just return false so let's head back to our main lib index file and here I'm going to define the function create a note which will be of type create note this will be on a sync function which doesn't take any parameter and here first thing is as always extracting the root the root directory then we since we are creating a new note we must be ensure that the directory note mark exists so we do this by using the function ensure here by passing the root directory of the application so now when creating a new note we want to show the end user a dialogue to basically select the name of the notes and also the folder of the of the notes to do that we are going to use dialogue from electron and this dialogue right here is the native dialogue that are implemented by the underlying operating system so we are gonna see it in action when we are going to having the implementation done so here we are gonna use the dialogue dot show sorry dialogue dot show save dialogue which is basically the dialogue that opens up when we want to save a file and here we can pass some option for example a title and we can name it for example new note we can pass a default path which will be the root directory and then we can also set default name for the newly created note in general we many applications use untitled or naming a file that has not been saved with a particular name great then we are going to specify a button label so the button label to confirm the operation and we are going to name it create and then we are going to specify some properties for example we want to show the override confirmation so in case we are trying to create a note with a file name that already exists we want the user to be aware that is going to override the content of that note and then we are gonna use the shows tag field to false because we are not interested with any tag field and also we are gonna filters we are gonna apply some filters in this case we want the file that are going to be created by this dialogue must be only of type with extension dot md and we can do that by specifying first a name that we are going to call markdown and then the extension which is gonna be md this will ensure us that every file created must be with the extension dot md a great great job actually when this dialogue has been closed it will return some result for example the file path of the newly created file and also a cancelled event so cancelled is basically a boolean that tells us whether or not the dialogue was cancelled so here what we can do inside an if statement say if the dialogue was cancelled or we don't have any file path we just return false but we want also to console log something on the console and say note creation cancelled okay here now we have the complete file path of the newly created path and we want to extract the file name and the parent directory to extract this information we can use path from the library path and use the parse function to parse this file path and this will return an object which inside will contain a name the name of the file which will rename to file name and also the year property which is basically the parent directory we need the diff directory to check that the file has been saved inside the correct folder because if the file gets saved outside the notepark folder the application will not be able to load that file so what we are going to say is that if the aren directory is not equal to the root directory of our application we are gonna display a message box or a message native dialogue with type error with title creation failed and also a message that is going to say all notes must be saved under root directory and then say avoid using other directories great so this will be our message and then we will return false since our creation has basically failed we can also go ahead because now we ensure that the file has been created in the correct directory and first we can just console log that the note has started to be in creating so creating note we are gonna also log the file path of the of the note and then we are going to execute the right file from fs oops right file and we are gonna pass the file path and we are gonna pass this content empty content we don't want the note to have any content inside when it is newly created and then finally we are going to return the the file name great at this point let's go inside the main dot index file and here i'm going to define the epc main dot handle name the event create note this will take all the arguments parameters of the function create note and is going to execute the function create note then we go inside the preload and define the create note which will be basically here we have to import the create note here we missed a comma and so basically as always we are invoking the the event create note to signal the main process to execute the create note function great so we had also to update our global interface and say create note we type create note great okay now let's go ahead inside our store and here let's search for the create empty note item that we already defined but that was using fake data so here what we are going to do is to remove this fake content and replace it with a wait a window dot context dot create note actually we have to specify the async keyword here so here basically we create the note and inside this title we return or the filename of the note or false in case we didn't create anything so if not title we are going actually to return and yeah actually this is the only thing that we need to update because the other remaining parts will be left as they were before so great now what we are missing is going inside the new note button and actually here we need to update this handle creation handler we need to specify that it will be an async function and specify an await here now we should be good to go so let's go here and let's restart our application okay now let's try to click on the new note button and you can see how the native dialogue in this case on macOS appear and it suggests me to name this note untitled and you can see that the default part is already not marked I can name this not my note and create it and you can see that the note has been created and I can put actually some content inside like hello from note hello from my note if I open up the terminal clear it out and do an ls you can see that we have the newly created note and if I print the content you can see that everything is working fine great actually I want to show you something else you can see that if I try to create a note and I high-heat cancel nothing happen and also if I try for example to save a new note inside for example my desktop folder and I try to hit create you can see that this message box appear and say all notes must be saved under users jonathan not mark and avoid using other directories and anything happens so great great job okay at this point the last piece of functionality that our application is missing is the delete note functionality and yeah let's go in to implement it but first let's close all of these tabs and let's go inside our charret types here I'm going to define a new type called delete not this will be a function that will take a title and will return just in this case will return a Boolean to say if our note has been deleted or not great so now let's head into to main lib index and here I'm going to define a new function called delete not which will be of type delete not this will be an async function that will take the file name and here as the other function we have to extract the root directory of our application and then we are going to use again the dialogue and now we are going to show a message box of type warning the title will be delete not the message will be are you sure you want to delete and specify basically the file name sorry the question marks should be attached we're going to specify also two buttons first will be the delete button and the second one will be the cancel button and here is important to note that the first button will be attached to the index number zero dot zero is delete button and one is the cancel button and this will be super important for the following properties which will be the default ID which we are going to set to one so default ID is basically the index of the button in the buttons array which will be selected by default when the message box opens and also for the cancel ID the cancel ID should beam up to the cancel button okay so at this point we can say that we can extract the response that we get back from this dialogue and here we can say that if the response is equal to one that means that the deletion has been cancelled so we are going to log a note deletion cancelled and we basically are going to return false because the deletion operation has been aborted otherwise we proceed with the deletion so we're going first to add a log which is going to say delete the note by name of the note that is being deleted and then we're going to call the remove function from fs extra and passing basically the root directory and file name along with the extension which should be a .md file and then we can just return true great so this is our notes deletion operation so now we should go back to our main index and define the epc main dot handle event and we are going to call this event delete not and actually here we can just update the suggestion from co-pilot here we are going to use delete not and passing all the arguments let me check that everything is working yeah great now let's add into the preload script and define here as well the delete not function here we can just improve the suggestion given by co-pilot great and finally update index dot d dot yes and define this delete not okay at this point let's go inside the store and let's find our delete not atom which is this one so here what we need to do now is basically call the function window dot context dot delete not passing the selected not title obviously when we are going to press the button delete we're going to delete the note that is currently selected and here we can first need to declare this as an async function in the async keyword and here we are going to extract the result which we are gonna call is deleted which will be a boolean variable which can be true or false obviously in case is not the operation has been aborted you can just return otherwise we basically leave everything as is because if you recall from before here we are basically filter out the deleted not so basically removing the notes from the jotai internal state and here we are basically the selecting any not so we want any not to be selected after we just deleted the currently selected not great last step will be go inside the delete not button making the handle delete an async function and just a wait for the delete not since this now will be an async function so now we should be have everything setting up so let's run our application and let's test the deletion so now I have selected the the mynot file that we previously created and let's try to delete them so the message box appears and say are you sure you want to delete my not if I hit cancel anything happen but if I hit delete the not is currently removed from their not list and also if I use my terminals and type ls you can see that my not is no longer here we can just make also another test let's create a new note which I'm going to call delete not create it and I'm going to type please delete is not as the content of the notes okay now let's switch back to our terminal let's do an ls here's the delete not file that we just created if we go back to our application and it is the not you can see that has been correctly deleted since is no longer here so great great job okay so now let's close all of these tabs and I want also to add a little bonus tip if we open up our application I want to show a welcome note in case a user does not have any notes so he can get a clue on how this not application work so in order to do this what we are going to do is first minimize all of the open folders then under the resource folder I'm going to paste static markdown file called welcome note and this is basically rebuild markdown note that I came up with where I basically explain how not mark works and we want to show this note if the as I was saying if the user have any notes to display so in order to achieve these features we need to go back to our source main uh lip folder and we are going to update the gets not function to handle this case so what we need to do after we get all the notes from the file system and we filter out all the file that are not in the dot md extension we're gonna have a new if statement where we are gonna say if is empty notes actually here we have to import this is empty is empty uh from low dash if we don't have any notes we are going to add this welcome note so let's start by logging some information on the console the console.info no notes uh found creating a welcome note great so now we need to retrieve the content of this welcome note dot md that we just created so let's go ahead and let's extract the content of this welcome note here we are going to use the read file from fs extra and actually here we can import the path of this welcome note import welcome note file from here we need to use the absolute path so dot dot slash dot slash again dot slash resources and then specify the welcome note dot md and since this is an asset file we are going to append a question mark with a string asset uh this string right here is a feature provided by it to managing static assets so this will be now as you can see a string containing basically the path of this welcome note dot md and we can reuse it here for reading this file so here we're gonna say welcome note file and we are gonna pass the encoding back to the file encoding uh great so now we have the content of this welcome note and the next step will be create the welcome note so await right file we are gonna specify the path of this file which will be the root directory forward slash welcome note dot md or actually we are gonna name this uh welcome dot md then specify the content of the file and finally the encoding which will be the file encoding and then we have to perform another operation which is pushing this newly created note inside the list of notes that then we are going to map to uh note info so here what we have just to do is to do notes dot push and then the name of the note which will be basically welcome dot md in order to be parsed by the get not info from file name actually here we are repeating the welcome dot md string two times so this is a good use case for extracting a new constant variable which we are going to name welcome note uh file name which will be welcome dot md and here replace it with welcome not file name and here same thing here not file name uh great so everything now should be work as expected so open up our terminal and you can see that inside our note mark folder we have two notes but if we have two notes the welcome note will not appear because we have just some uh notes to to read from so i'm going to remove these two notes so remove note one dot md and note two dot md great now we don't have any content and so if we try to launch our application yarn dev you can see that our welcome note is displayed correctly and if i'm going to open it up you can see that inside we have these reformatted content that explain basically how note mark works i think that we have just completed our application the last step that i want to show you is how to build this application for production and how to actually build it for different operating system so let's close this file and let's open up the package.json inside the package.json you can see that we have different scripts available and amongst the script we have the build script that basically first run a type check of the entire application and then run an electron width build for building our application for production we have also a post install script that basically runs the electron builder for install the all the app dependencies and this electron builder basically works with the file electron builder dot yaml that we are going to see soon and then we have three different script one for windows one for mac and one for linux these three scripts will generate a distribution folder which will contain all the installer for installing our application as a desktop application and this actually can be released in the different store for example the app store in case of mac us so let's give it a try but first we need to update our electron builder yaml configuration so inside this configuration file you can see that you can managing how the final build will looks like for example you can set an application id a product name all the file to include or excluding that from the final build all the resource folder and so on so we can leave it like this because it's already prefilled with a configuration that work for us so let's open up a terminal let's clear out the console let's minimize it a bit and the first script that we are going to run is yarn build so you can see that is running the type check everything is correct and now you can see the scripts as created the build for production and is using it as a bundling tool and you can see that it generates this file under the out folder if we go here it basically generate three bundled files for every process one for the main process one for the preload and one for the for the renderer project great we can also run this production build by running yarn preview sorry by running yarn start and our application is working fine that was it for the application the last script that i want to show you is build for mac since i'm using mac os i'm going to run yarn build mac and this should generate a separate folder which will contain all the necessary file in order to this application to be installed inside our mac os operating system and it will take a little bit of time to generate all the files so let's wait until it's done okay the process has been completed and you can see that the output is this this folder let me increase this a bit and see that it generates all the necessary file in order to install the the application and amongst them we have notepark the version of our application and we have the dmg file which is an installer for to install this application as a mac os desktop app so if we are gonna run it we are gonna have the application installed inside our operating system great great job okay guys so that's it for our non-application and for this video down in the description you will find the github repo for this project and also a link to my discord channel for getting in touch with me and also for ask me anything as always if you like the video please leave a thumbs up subscribe to the channel if you haven't and we will catch to the next video