Choosing a WYSIWYG editor0:00
Now, our forum is looking... bland. It's just paragraphs and paragraphs of the same text, and the formatting looks identical. It's boring to look at, if I'm being frank. And when it comes to something as emotive as movies, well, users want the ability to format their text. They want to add bullet points with their favourite points from a movie, or a numbered list of their top movies of the year, or they want to make certain things bold or italicised or use headings to separate the forum post up into sections. We can facilitate that, and I think it would be a great idea to do so. A perfect format for this sort of customisation is markdown. So, if the user, for example, uses a double hashtag, and then a space, and then they enter a title, that would be equivalent to a h2 when you convert it to html. If they want to do bold, well, they use double star, and then they enter the text, and then they
then they enter a title, that would be equivalent to a h2 when you convert it to HTML. If they want to do bold, well, they use double star, and then they enter the text, and then they close with double star, and now they have bold formatted text. So, we could just pass this text area as markdown, and it would work. But, you have to remember, we're not programmers on this forum. In something like Laracast, on the Laracast forum, for example, we are programmers. We're all developers, so most of us understand how to write markdown. You don't need anything special. But, in the world of forum posts for films, most of the people on that forum are not going to understand how markdown works. So, we need more of a what you see is what you get editor, like a text processor or a word processor that will make it much easier for them to add the correct formatting without having to understand
Installing TipTap dependencies1:34
what you see is what you get editor, like a text processor or a word processor that will make it much easier for them to add the correct formatting without having to understand markdown. After searching around and trying various different editors, I have decided on TipTap. TipTap is a headless editor, which means that it doesn't come with an interface. It just provides all of the functionality under the hood, and then you provide the interface. That can be scary at first, but don't worry. You'll see how simple it actually is once we get started. So, let's head to the installation guide, and the nice thing is it has installation steps for Alpine and php. So, if you're using the Livewire stack, or if you're using PlainBlade, there are still options available for you. We're going to jump to view three. We'll go to install the dependencies, and I'll copy this npm command here, and then let's run
there are still options available for you. We're going to jump to view 3. We'll go to install the dependencies, and I'll copy this NPM command here, and then let's run that inside our project. Whilst that's installing, there are three libraries that we're installing here. One is the view 3 wrapper around TipTap. The second is called PM, which is ProseMirror. ProseMirror is the underlying low-level library that gives TipTap the functionality it actually needs to build out an editor. And then we have the TipTap starter kit. Basically, because TipTap is modular, you have to install, for example, support for bold text, support for underlines, support for strikethrough, and the starter kit gives you a bunch of very common modules that you would want to use in almost every project. So, these three items should help us get off the ground quickly. With the dependencies installed, let's go
Building editor Vue component3:00
common modules that you would want to use in almost every project. So, these three items should help us get off the ground quickly. With the dependencies installed, let's go to resources/js/components, and I'm going to create a wrapper component for our editor. Let's call it the MarkdownEditor. We could call it the TipTapEditor, but I sort of like to abstract away what actually is implemented under the hood, much like you would inside your server-side php code. So, there we have our template. We're going to have our setup script, and inside our template, let's have an outer wrapping div, and then editorContent, I believe, is the component we're actually looking for. We need to pass editorContent and instance of an editor, and in Vue 3, if you're using the composition API, well, there is a composable provided by this Vue 3 library called useEditor, which we can
and instance of an editor, and in Vue 3, if you're using the composition API, well, there is a composable provided by this Vue 3 library called useEditor, which we can pass configuration options. We'll look at that in just a moment. So, we'll pass that into the editor up here, editor equals editor, and that should more or less get us off the ground other than including that starter kit. The starter kit is an extension, so we need to come into the configuration object here, mark an extension, which is an array, and the extension is starterKit. Awesome. That should be enough for us to actually see something on the front end. Let's go to our CreateComponent for creating a forum post, and where we have the text area for body here, we'll go ahead and drop in our markdownEditor. Alright, let's go back into our project, and you'll note there's just a little bit of extra spacing.
Adding custom v-model4:23
the text area for body here, we'll go ahead and drop in our markdown editor. Alright, let's go back into our project, and you'll note there's just a little bit of extra spacing between the title and the body. That's because right here, you can see we have our unstyled, completely bare bones tip tap editor. So, if I type hello world in here, yes, indeed, it is working. The next thing I want to tackle is wiring up v-model for the tip tap editor, so that as we type in here, we'll see the raw output in the text area below, and vice versa. Now, when we've discussed custom v-models in the past, well, we've just copied and pasted text area, and that's worked absolutely fine, but I'd like to jump into a little more detail this time. So, I'm going to do it manually. Let's jump into our markdown editor, and here at the top of our script, I'm going to define a new const called props, and that's equal
detail this time. So, I'm going to do it manually. Let's jump into our markdown editor, and here at the top of our script, I'm going to define a new const called props, and that's equal to defineProps. I'll use the object syntax, and I'm going to call the key modelValue, which have a default of an empty string. Now, it's vital that you call it modelValue like this, because that is the reserved keyword for v-model. Okay, after we define our props, we need to define an event. So, let's say const emit equals defineEmits. This is an array, and the name of the event we need to define is update:modelValue. Again, it's vital that you use this syntax. It has to be this syntax that is a reserved event for v-model in Vue. So, let's start with the emit event. Anytime our content in the tiptap editor changes, we want to inform the parent or whatever's using the v-model.
for v-model in view. So, let's start with the emit event. Anytime our content in the tiptap editor changes, we want to inform the parent or whatever's using the v-model that there has been an alteration. We're going to come down to the useEditor config, and we can define a hook called onUpdate. onUpdate is a closure, and basically, we want to say, well, emit the following event. So, this emit constant here is actually a closure that we can use. It's a callable, and we want to emit update:modelValue, and the second parameter is going to be the updated content, which is editor, and again, we need to grab the value of it, and if it exists, we can get the HTML. Now, I know we said we wanted to use markdown. Don't worry. We'll get to that in a moment. For now, we're just going to output the HTML. So, to see this in action, we can jump back into our create
wanted to use markdown. Don't worry. We'll get to that in a moment. For now, we're just going to output the HTML. So, to see this in action, we can jump back into our create component, and let's go ahead and wire up v-model to form.body. So, if this is correct, we should see the text area filled out with the same content as markdown editor. We'll go back to our browser. In the tiptap editor, I'll say hello world, and sure enough, you can see that this is now wrapped in a <p> tag because it's outputting HTML in real time. If I use maybe a hashtag and then hello world, now note that we're in a <h1> tag instead. So, that's working perfectly, but you'll note that if I change the text area, nothing changes in the tiptap editor. So, we need to use the props to go the other way around inside our markdown editor. Let's jump back in there, and at the bottom here, I'm going to define
in the tip tap editor. So, we need to use the props to go the other way around inside our markdown editor. Let's jump back in there, and at the bottom here, I'm going to define a watcher. I'll define a watcher using a closure, and I'm going to listen for props.model value, and any time that value is updated, well, this closure here will be called, and it's past the new value. So, let's say, give me the editor, and again, we need the value of the editor because it's a reference in view, and if that value exists, then I'm going to call a command on the editor called setContent, and you can see where we're going here. We set the content to the new value, and we also need this watcher to be fired when the view component is first mounted because we receive an initial model value. So, to do that, I'll pass a third configuration option to watch where we can set immediate to true, and that
component is first mounted because we receive an initial model value. So, to do that, I'll pass a third configuration option to watch where we can set immediate to true, and that will perform that initial check for us, and then continue to watch the props.model value. Okay, let's see if that works. So, now, as I update the text area, you can see that the tip tap editor alters, and as I update the tip tap editor, the text area alters. Now, we have a little bug here where the space bar, I'm pressing the space bar, you can't see, but I'm pressing the space bar, and the tip tap editor isn't working. I think that's because we need to add an if statement to our watcher. Basically, if the content is the same as before, we don't want to set the content in the editor. So, to do this, let's say if the value is equal to editor.value, and then we can say .getHTML. So, if nothing
the same as before, we don't want to set the content in the editor. So, to do this, let's say if the value is equal to editor.value, and then we can say .getHTML. So, if nothing has changed, just return early, and I'm pretty sure now, yeah, there we go. My space bar is working again. Everything works as expected. Wonderful. So, we now have successfully set up a custom v-model using those two items, a prop called modelValue and an event called update:modelValue. Now, you can use that exact same functionality in any view component you create. So, it's a great one to keep in mind and to understand how it works because you can build so many cool things using custom v-models. Let's now tackle the fact that we output in Markdown, but we actually want to output in HTML. If we look at the tiptap documentation, it looks like we've hit a dead end because it says that tip tap
Enabling Markdown output9:36
fact that we output in Markdown, but we actually want to output in HTML. If we look at the tiptap documentation, it looks like we've hit a dead end because it says that tiptap doesn't support Markdown as an input or output format. So, why have we used tiptap at all? Well, I really like tiptap. It is so powerful, and it's such a shame that they don't give us some Markdown support, but thankfully, somebody in the community has already gone ahead and done that. We have a tiptap-markdown plugin that we can use in order to provide the exact support we're looking for. So, let's go down here and we'll copy the command to install npm install tiptap-markdown. And once that's installed, where we define our starter kit extension, just underneath, I'm also going to import the Markdown extension like so. Now, where we've said editor.value.getHTML, we just need to update
our starter kit extension, just underneath, I'm also going to import the Markdown extension like so. Now, where we've said editor.value.getHTML, we just need to update those references, and what we actually need to say is editor.value.storage.Markdown.getMarkdown. So, a slightly different syntax there, and note that I've updated it on this line here and on this line here. But setting the content according to the plugin documentation is exactly the same. So, what I would expect if we go back to our application, and I now start typing maybe a title, hello world, is that in the raw output text area underneath, we have Markdown formatting. Let's use a list instead. So, this is 0.1, this is 0.2, and yeah, you can see that's being formatted as a list. Now, because tiptap is headless, we don't see any styling in the tiptap editor itself currently. And
Styling editor with Tailwind11:10
this is 0.2, and yeah, you can see that's being formatted as a list. Now, because TipTap is headless, we don't see any styling in the TipTap editor itself currently. And this is a little bit of an annoyance because obviously we like some indication as to the fact that we're currently inside an unordered list. So, back in the TipTap documentation, they have a guide for styling, and I'm pretty sure in this guide that, yeah, look at this, editor and with tailwindcss. So, you can pass editor props, attributes, and then you can load in classes that you want to apply to the editor itself. I'm going to copy that section, we'll go back into the IDE, and just underneath the extensions array, let's drop editor props in. Alright, in case you don't know what prose is, prose is actually a tailwindcss plugin that is official, and it tells you here exactly what it does. Beautiful
drop editor props in. Alright, in case you don't know what prose is, prose is actually a tailwindcss plugin that is official, and it tells you here exactly what it does. Beautiful typographic defaults for HTML you don't control. So, we don't know what's going to be inside that editor, meaning that we can't have tailwind classes to it. But by using the typography plugin from tailwind, tailwind can take care of that for you and give us some gorgeous defaults that just look right out of the box. So, if you're using Jetstream and you go to tailwind.config.js, you'll probably find that you already have this installed. It's already configured as a plugin for tailwind. Now we have it installed, we'll go back to markdown editor and we'll leave prose and prose-sm in. So, prose is going to say I want tailwind to take over and format everything in here, and the prose-sm modifier is going to say
editor and we'll leave prose and prose-sm in. So, prose is going to say I want tailwind to take over and format everything in here, and the prose-sm modifier is going to say just make everything a little bit smaller, which is exactly what we want. I'm going to get rid of all the rest of this, and then let's go back to the browser and see what happens. So, we'll say this is a bullet point, and yeah, you can see tailwind's already doing its magic. This is another. Let's do an ordered list. So, this is a number, this is 2. Look at that. That's really good. Magic, just by adding the typography plugin. You'll see that it's pulled in the width of the input. I don't really want that. So, I think we can set max-width of none, and yeah, now you can see it's filled up the entire width again. I also think we need to style this so that it has a similar look to the text area and the inputs that
width of none, and yeah, now you can see it's filled up the entire width again. I also think we need to style this so that it has a similar look to the text area and the inputs that we already have. So, let's do that next. We'll start at the top on the outer div. I'll give it a class of bg-white, rounded-md. How does that look? Yeah, already that's starting to look better than it was before. Let's jump into our custom textarea and see what classes that uses. border-0, py-1.5. Yeah, I'll take those. What else do we have on here? shadow, ring. Yeah, I want everything to do with ring. So, we'll drop those on as well. I don't think we use placeholders in TipTap the same as we use placeholders in standard inputs, so we'll leave that. I'll grab everything to do with focus and rings, and we can drop that in there. All right, let's see how that looks. So, we'll come back. Oh, look at that.
inputs, so we'll leave that. I'll grab everything to do with focus and rings, and we can drop that in there. All right, let's see how that looks. So, we'll come back. Oh, look at that. That's already starting to look loads better. I don't think we want that PY 1.5, do we? We actually want to put that down here. So, PY 1.5, and I think maybe PX 3, just to pull the text in a little bit. Yeah, look at that. That's awesome. So, now as we type hello world, it's in line with the text area underneath, and we have the same look and feel for our TipTap editor as we have for the rest of our inputs. I want to be able to customize the height of this editor, but just for now, I'll set a minHeight on the editor itself, and I'm going to use the square bracket syntax to set a custom value. So, minHeight of 512 pixels, at least for now, and there we go. That's much better, and then in our create
I'm going to use the square bracket syntax to set a custom value. So, minHeight of 512 pixels, at least for now, and there we go. That's much better, and then in our create component, I'm just going to give ourselves a bit of spacing, so maybe mt-2, so we can differentiate between the two areas, and eventually, we'll be removing this text area entirely, but for now, it's a good indication of what's actually coming out the other side until we get to storing this in our database. So, now we have an awesome real-time markdown editor. I could add a title, so this is a title. I could go ahead and make something bold. This is bold, and I can make something italic, so this is italic. However, as we mentioned at the start of the episode, I have to know how markdown works to use it currently inside these areas. This movie forum is for the average Joe. We should have a toolbar, as you would.
at the start of the episode, I have to know how markdown works to use it currently inside these areas. This movie forum is for the average Joe. We should have a toolbar, as you would have in, say, Microsoft Word, or Google Docs, or whatever you use, to easily be able to apply formatting to different pieces of text. We'll tackle that in the next episode.
