 Hi. I'm Chris. I'm an engineer at Crowdstrike. I'm obliged to say we're hiring. If anyone's interested in looking to work on Ember apps, we've got lots. The majority of us are based in London, and we're purely Ember on the front end. If you are here, you're probably interested in that. My talk is called, Want to be content rich? I needed a headline that sounded more interesting than text editing. Sometimes text editing experiences are just not as good as they should be. When I drop an image into a text area at a particular point, this is not the result I was hoping for. There's no visual feedback. It's just put the path that has been generated to that image at the bottom of the text. So it's not even where I want it in the document. There's many types of editors you can use in your apps, either open sourced or purchased, but none of them seem to do everything well enough to become the single best choice. One format that many editors use that we're all familiar with is Markdown. Markdown is often used as a speedy way to add content creation into an app. It has widespread adoption and therefore tooling across many platforms, but in anything but the simplest use case, it quickly becomes awkward to use. Because it becomes awkward rather than impossible to do things, users will often just develop workarounds that allow them to manage, like using an external editor that offers syntax validation and a preview. They might choose to use this to make their changes in the editor before pasting them back into your app. You won't get any bug reports because nothing is broken, but it's very likely your users are not having a good time using your product. Do you have to use Markdown? No. Use mobile doc instead. So this is what I used to think of when I first heard the term mobile doc. You have to remember in the States they call these things cell phones, not mobiles, and the authors are mobile doc or American, so that kind of makes sense. But I put this image in your mind now. Mobile doc actually refers to a portable document format. Using it will allow you to create an editing experience that compares to the best editors you've used on the web. And it's not as complicated as you might think to build a rich experience for your users. I'm going to show you how right now. This is an app I built just to demo to you what mobile doc is and how it works. The boarded area at the top is the editor, and the area at the bottom shows the mobile doc created by the editor, rendered in a number of different ways. Currently it's showing the JSON structure that is produced when you save a document in the editor. This is mobile doc. The name actually refers to a specification for a JSON structure. There are a number of other parts to the ecosystem that build upon this specified format to help you produce your user experience. So zooming in on what was in the bottom of the screen there, this is what a blank mobile doc looks like in terms of structure. You can see it's an object that contains keys for atoms, cards, markups and sections. The markups array lists any formatting used and the sections array contains the actual structure of the doc. We'll come back to atoms and cards later. Let's look at how the structure changes as text is typed into the editor. We start with one empty paragraph in the sections array. As the text is entered, you can see the mobile doc structure update. Now let's add some basic formatting. The user has used the shortcut command B to toggle a strong text before typing. You can see the structure update at the bottom of the screen. Let's look at those changes in a better formatted example and break down what's happened. The strong markup has been added to the markup array. In the sections array, the section of type one indicates this section is a markup. The first zero in the indented array is the markotype zero indicates text. The array with a single element of zero indicates the markup used. Position zero in the markup array is strong. The one that follows that indicates the markup is closed and finally the text that's contained within the section. Let's make that slightly more complicated and see what the structure looks like the one word in the sentence is strong. If we format that a bit better we can see that there are now three markers in the section. The first is text denoted by the first zero using no markups denoted by the empty array and is open indicated by the next zero. The next marker is different in that it refers to the strong markup. There's a zero in the array versus being empty on the previous marker and it's closed. The third marker is structured the same as the first but with different text. For reference, here's a list of all the markups that are supported. That's a pretty basic overview of the structure. Let's leave it there and look at the bigger picture. There are three things that build on this mobile doc standard. Parsers, renderers and editors. Parsers allow you to manipulate the content when it's being pasted into your editor before it's saved into the mobile doc. Renderers allow you to present a mobile doc in different ways. Editors allow you to build the user experience for the content creators. Let's talk about editors first. The one you've seen in the demo is Ember mobile doc editor. It uses a library called mobile doc kit. This is the framework agnostic library for building mobile doc editors. Ember mobile doc editor provides an abstraction using Ember components over mobile doc kit. So if you're used to using Ember, this makes the process of building out additional functionality in the editor very familiar. You don't have to use this editor though. That's the point of mobile doc kit being a separate vanilla JS library. In this demo, I've chosen to use Ember mobile doc editor. Let's step through the code that's behind the example you've seen. Here's the template for the editor. That's everything on the top half of the screen of the demo app. As you see, we pass in a mobile doc for the editor to initialise itself with and an action to be called when the document is changed. In the block of the component, we pass the buttons we want to appear in the toolbar. Mobile doc editor and mobile doc section button are all provided by the Ember mobile doc add-on. And the component JS that backs that template looks like this. It's empty. I'm just using a convention of listing the attributes that can be passed into the component at the top of the file and setting the component to be tagless. Here's the output component. That's everything in the bottom half of the screen on the demo app. There are three output formats to choose from. So far you've seen the one on line nine. The one on line 14 is Ember mobile doc DOM renderer. That will give you a faithful reproduction of what you see in the editor, but without the ability for any edits. The last one is on line 20, but we can see better what's going on with that by looking at the component JS. Here you can see on line 21, the computed property responsible for the mobile doc in text. On line 23, a new renderer is created. You can see that it is imported from mobile doc text renderer. Let's see what these do in the demo app. Returning to our strong text example, we can see that when switching to the rendered output, it does faithfully reproduce what's seen in the editor. Then switching to the text output shows that the full text without any formatting. This is a good illustration of renderers. The text renderer is simply ignoring anything stored in the mobile doc text that does not apply to it. So that's pretty cool. We already have a basic what you see is what you get edited that stores content in a format that is concise and can be used selectively to render different results. It's not rich yet, though. Mobile doc provides two mechanisms to achieve this. Atoms described here and cards described here. Now, because we're using ember mobile doc editor, we can use the helpers it provides to create atoms and cards as components. Let's build a card component now. This is going to be quite a lot of code. The first thing we're going to do is create a button that the user can click to add the card into the editor. This is what the component.js for the button looks like. It contains a single action that's called when the button is pressed. On line 12, we tell the editor to create the card in edit mode. The first argument shown on line 13 is the name of the card. The next argument is the payload to be set on creation. Cards have an edit mode that shows a different view to the user than the rendered version. It's not used when the card is rendered by the DOM renderer. The template for the button looks like this. Not much to say about it, it's just a button. The block is used to allow you to pass optional text. This is the template for the code block editor. Now, as you may have noticed when we created the button, this card is going to be for adding code blocks to our doc. What we want this card to be is a full what you see is what you can get experience, just like the main editor, except styled like a code block. How are we going to do that? Let's put another mobile doc editor in the card editor. You can see that on lines 2 to 6. We're going to add a save and cancel button to allow the user to either reverse the action of pressing the code button or save their changes. You can see the buttons on lines 8 and 9. The component.js for that looks like this. It's a bit small to be able to read, so let's split it up. In the first part, you can see we're getting a lot of attributes passed into the component. It's worth noting that these are passed to the component from the editor because it's the editor that actually calls this component. It's the actions that we're really interested in, though. Interested in, though. The save card action either takes the payload that's been edited by the user or, if that's undefined, the payload that was present when the editor was invoked. If the card has just been created, that would be the payload passed when the card was created by the button press. If the card has been saved previously, the payload will be the one stored last time the document was saved. Finally, the save card action we were passed by the editor is called. The cancel card action takes the payload to see if this card has already been saved previously by fetching the created by button key from the payload. Remember we passed this payload in when the button was created, when the button to create the card was pressed, sorry. If it exists, we remove the card when the cancel is pressed. If it does not exist, we cancel the card instead. That just undoes our edit session. We also need to create a component that will be used to render this content when it's not editable. It will be used by the DOM renderer but also by the editor when the card is not being edited. This is what the template looks like. Line 1 calls the edit card actions passed into this component by the editor. And on line 3 you can see the mobile doc DOM renderer again. This is to render the content saved in the payload of the card because that is the mobile doc nested in the payload. The component for it looks like this. I've split it in two again so it's a bit bigger. As you can see, we've passed a load of attributes from the editor again. Amongst them is the edit card action we're calling from the template. The only other thing in that file is a check on initialisation to see if there's a content saved in the payload. If not, we set the content to be a blank mobile doc. We need to register the card with the editor. We do that in the component.js for the editor. To be clear, this is not the card editor but the editor itself, so the bit that's responsible for the top half of the screen. Now it looks like this. We've added a compuser property on line 8 that returns an array of cards. We use the create component card helper provided by EmberMobileDoc editor to do this. As you can see, on line 10. The template has then changed to pass that card's array into the EmberMobileDoc editor component. You can see that change on line 3. The output component.js, the bit that's responsible for the bottom half of the screen and the demo app needs to be changed to create a card names array, lines 13 and 17 of that change. Then in the template, we pass that array into the DOM renderer on line 8. That was a lot of code. Let's see how that looks in the app and what the changes have done. You can see the editing versus rendering components being used here. That's the editor. They fill in some text. That's going to flip to the rendered component. You can see the raw content of the doc updating from the payload. That's set when the button is pressed to save the payload. Beyond that, it's hard to see how it's changed from that view. Let's break down the changes to the structure of the doc. It's pretty small, but hopefully you can see this. The shape is the bit that's interesting, so maybe just squint a bit. Line 33, which is right at the bottom, shows where the card was created relative to the text. Just below it. You can see the section type is 10 indicating a card. The only valid section types are 1 for a markup, which basically means text. 1 for a list or 10 for a card. The 0 next to it means to use position 0 in the card array, our code block card. That's on line 5. The name of the card is specified on line 6. The payload starts at line 7. As you can see on our code block content key, on line 8. Line 9 is the start of the mobile doc we saved from the editor in the card. That's the mobile doc inside the mobile doc. Now let's see what's happening with the two renderers I mentioned earlier. The render tab, we can see the DOM renderer being used. You can see it's replicating what's shown in the editor. When I click on the text renderer, you can see that the text content from above the code block is shown, but nothing from within the code block is. That's because the text renderer doesn't know how to handle the code block card's payload. We can fix that, though. In the same way we passed the cards to be used into the editor and the DOM renderer, we also have to pass them into the text renderer. Instead of a component path, we'll need to reference a file with the following structure. The name of the card has to match the name contained in the mobile doc, which you can see on line 4. The type has to be text for the text renderer to use this. Line 6 contains the render function, which is what the text renderer will call any time it encounters a card of this type. The treatment we give here is simple. We extract the code block content key from the payload. Because this is our nested mobile doc content, we then create a new instance of the mobile doc text renderer to render the text contained within it and then pass that on as the result of the render function. We then need to register this file with the text renderer we're using in the output area of the demo app. I've cut out the unchanged parts of this file to try and make the example a bit more concise. You can see the file we just created being imported on line 2. The array of text renderers being built on line 7, and then it's passed into the renderer on line 18 and the results are. Now with the text from the code block is rendering along with the other text. So now we've successfully told the text renderer about our custom payload. But another problem that comes with creating a custom payload is being able to populate it when the appropriate content is pasted into the editor. That's where parsers come in. Mobile doc kit already comes with parsers for the types supported by mobile doc. So pasting content from HTML works fine for those types. But for our code block card, if we copy the tombster and paste it in, not so much. But we can fix that by writing our own parser. Here's the parser file. Line 2 is basically saying if this is not a root node and a pretag do nothing, because that's all we're interested in, otherwise grab the text content of the node that has been pasted, that's on line 7. The content is passed to the function on line 17. It inserts the text into a mobile doc with a single paragraph. We then build a new card section on line 10 for our code block card, passing the payload created by the function under the code block content key. Remember we're working with mobile doc inside a mobile doc here. We then add the section to the doc on line 12 and then mark the node as finished. And we have to modify the editor component, JS, to build an options object. That's on line 16. We then add the names and array of parser plugins, the one we created on line 19. And you can see where we import it on line 2. And then pass that options object to the editor in the template line 4. And if we try the same thing again, now we get one perfectly proportioned Tomster. Also replicated perfectly in the DOM renderer and the text renderer. So now we've made our own custom content that's editable via a custom component. We've added support for it to be rendered from renderers. And we can even manipulate the content pasted into the editor. In the format we specified, part of the card as part of the card, the one thing we've let to talk about is atoms. The way mobile doc does custom inline content. Let's create an atom that allows logos to be used instead of text. Here's the component JS we're going to create for the atom. This is just for transparency in this case. We don't actually need to do anything here. But as you can see, we have access to a load of attributes passed by the editor. The template is also simple. An image tag that uses what's stored in the image URL key of the payload as the value of the source attribute. Atoms don't have an editor interface by default, so we don't need to make a component for that. We could create a button on the toolbar that would let users add logo atoms, but let's try a different approach. Let's create an action that we'll call when the editor is created. This allows us to use the onText input hook on the editor shown on line 8, and then specify some text to match shown on line 9. Then what to do when that happens on line 10. We're going to replace the match text with a logo, so the first thing we do is delete the correct spaces back from the cursor position. This is done on lines 11, 12 and 14. Then we add the atom into the editor surrounded by spaces on line 16 to 24. Line 18 specifies the atom name. Line 19, the text to use in place of the atom when required. Line 20 to 22 are the payload for the atom. It contains the image URL for the logo that should be used to replace this text. Then, much the same way as we built the cards array, we need to build the atoms array. This time we use the create component atom helper, again provided by EmberMovildoc editor. Then we need to pass the atoms array into the editor, shown here on line 3, and make our action get called when the editor has been created. That's on line 5. We need to create an atom names array, just like we did for the cards array earlier, and then pass that collection into the DOM renderer on line 7. Then we get this. It shows correctly in all the renderers, but there's one last thing I want to mention, and that's card options. Perhaps badly names, as they're also accessible for our atoms, they can be very useful as they're provided at render time, and can be used to make decisions about how to use what's stored in the mobile lock. Let's change our atom to be able to render text, emojis, or images. This might not be the most realistic example, but it should illustrate the power of options. In the atom component, we'll decide what to render based on the options passed to us. Line 14 and 15. Then we need to build a card options object shown on line 10. I'm going to set the key of logo atoms options to the value of logo atoms options, which is coming off an input we're going to put on the template. That's shown on line 4, and it'll appear just above the output area in the demo application. I also need to pass the card options to the DOM renderer. You can see that on line 12. Finally, we need to set the emoji to use when our text is matched. You can see that on line 22. Both an image URL and an emoji are now written into the doc as part of the payload, but the options are passed at render time. That logic in the component is then applied. Let's look at that working. We can see an emoji there in the raw mobile doc at the bottom of the screen. If I change the string that's bound to the logo atom options, you can see the rendered output change. I can choose either an image, text or emoji, or without editing the actual document. This example is very simple, but hopefully makes clear that these two data objects can be used separately or together. One baked into the doc, the other only required at the point of render. A couple of other examples could be alternative or additional content for different user roles. The user roles will be passed in as the options or charts that are passed fresh data right before they're rendered. You could leverage this a lot. Hopefully what I've demoed has shown that mobile doc is easy to get started with and really does allow you to build whatever you want for your users. All the code I've shown here is on GitHub, so give it a look when you want to get started with mobile doc. Thanks for your time. Any questions? Can you create tables? Does it support tables, I guess, to start with? No is the answer. It doesn't do tables, and it doesn't do nested lists, but you can quite easily polyfill that by building a card, basically. Then you have the option to choose how to render that as well. Yes, it does, but not out of the box. Once you get going with this, it's pretty easy to do that stuff. The application where you're using it for a crowd strike, we've polyfilled both nested lists. We've filled that gap with cards for both nested lists and tables. The renderers are more like the format you choose, so I guess if I was going to try and do another renderer, you could maybe do a PDF renderer or something like that, that would take the content of the doc and then try and render that as a PDF. But then you only need to talk in terms of... If you're just talking about web, then you'd just be talking about the card that is rendered in the editor or the DOM renderer, and then you have control over what that is. It's just a component like anything else in Ember, so it can literally be anything. You can put a whole app in there if you wanted to. There's no limit really to what you do with it. But I guess the good thing is there's that separation between configuration that can be passed in with options and then what's actually persisted. I guess like in much the same way you're going to write stuff that can be called to a database for your app, you can write that into the doc, but it means you don't have to store anything that's not required. There's no styling in there, so you're not bound that way, so if you want to do a refresh of all your content, there's no styling stored in there, so you can just completely change everything, how it's rendered, and even if there's stuff in the doc that you're not leveraging already, you can change that as well. Thank you very much.