Introducing Marked.cc

November 21, 2019

Today I launched Marked.cc – where you can create and share beautiful images of your notes. It was inspired by a tweet from Alex.

This is the first side project I launched over the web. Although I’ve been working for several years in product development, unfortunately I never published anything I was working on, and I decided to flip the coin and work in public. However small this project might be, it does represent a complete project that I can talk about. And that’s more than enough to launch it and write about it. It is a first-time experience for me, so I will be monitoring its consequences, and keep launching other products accordingly.

I’ve written down several notes during the work process – enjoy the read!


What is Marked?

Marked helps you create beautiful images for your notes, just like what Carbon does for your code. When I came across Alex’s tweet, the idea flashed immediately and I decided to build it as a side-project.

Marked.cc


Technical Highlights

Marked is built using Vue.js – the beautiful framework I have been using for years. As it represents a simple yet powerful framework, I couldn’t be more grateful for the development experience it has been providing.

Exporting DOM elements

There are mainly two ways to capture DOM elements – on the client and on the server.

Client-side rendering causes some issues due to the different rendering engines across the browsers, which causes the exported images to be slightly different from one browser to another. While if it was rendered on the server, the quality of the image would be the same regardless of the client.

To avoid running a dedicated server function, and to save up some time, I went with the client-side solution regardless of the quality issues.

Dom-to-image

I searched for the available packages that render DOM elements into images, dom-to-image was the choice. One issue I faced with dom-to-image is its lack of support for Safari v11.0 and below. The suggested solution is to render the SVG of the DOM elements on the server-side, which, as I mentioned, didn’t want to implement currently.

Highlighter.js

Technically, the main job for the highlighter is to wrap the selected text with span element and giving the required classes. The packages I searched for were either full of advanced features that are out of, or not suitable for, my needs.

So I implemented the single-line highlighter in its simplest form:

  • Highlighting normal text. Highlighting text

The implementation of the highlighter involved around the Range API. The range represents all the content (nodes and elements) that’s covered under the current selection.


    let range = window.getSelection().getRangeAt(0),
        span = document.createElement("span")
    
    span.classList.add('highlight--yellow')
      
    // Remove the range content from the DOM and append it to the span
    span.appendChild(range.extractContents())
    
    // Insert the span element in the place of the removed content
    range.insertNode(span)

What range.extractContents() does is basically extracting (removing) the selected content from the actual document into a seperate DocumentFragment (Aka: a container for the nodes). The extracted document fragment will be then appended to the span element, this means all the children nodes of the the document fragment will become the children of the span element.

  • Mark highlighted text with different color.

In this case, the highlighted span element should be splitted into three different elements, giving each one the suitable class name.

  • Dehighlight if the selected text is already highlighted:

This is a special case of the previous one, the span is splitted into three elements, but the middle one is de-marked.


    // ...
    // ...
    function mark(class_name) {
      let selection = window.getSelection(),
          range = selection.getRangeAt(0),
          is_same_element = range.endContainer.parentElement == range.startContainer.parentElement,
          is_highlighted_element = selection.anchorNode.parentElement.classList.contains('is-highlighted')
    
      // Check if the selection starts and ends at the same `span` element, 
      // and if it is a highlighted element
      if (is_same_element && is_highlighted_element) {
         let fragment = range.cloneContents(),
            selected_text = fragment.textContent,
            parent_element = selection.anchorNode.parentElement,
            parent_text = parent_element.textContent,
            splited_parts = parent_text.split(selected_text),
            should_demark = parent_element.dataset.class == class_name
            
        // In case the text is highlighted with the same new highlight, demark it
        if (should_demark) class_name = 'highlighted--empty'
     
        parent_element.remove()
        
        // First part of the old highlight
        range.insertNode(this.generate_highlighted_element(splited_parts[1], parent_element.dataset.class))
        
        // The new highlight
        range.insertNode(this.generate_highlighted_element(selected_text, class_name))
        
        // Second part of the old highlight
        range.insertNode(this.generate_highlighted_element(splited_parts[0], parent_element.dataset.class))
            return
          }
      }
    
    function generate_marked_element(text, class_name) {
      let span = document.createElement("span")
      span.dataset.class = class_name
      span.classList.add('is-highlighted')
      span.classList.add(class_name)
      
      span.appendChild(new Text(text))
      return span
    }

That is the code for a single-line highlights, but for multiple lines the logic got much more complex, and will not be covered in this article.


Upcoming Features

There are mainly three additional features I’m interested to implement:

  • Offline support – I’ve never built offline apps, it’s a good opportunity to learn new things.

  • Note sheet design – Marked in involved around the design of the note sheet, the user should have more options to customize the look of the note sheet. Multiple colors, various fonts, and different predesigned templates.

  • Server-side rendering – I may also run a dedicated server function to render all the images for better results. It’s not going to be dedicated to this projects though, I have several projects in mind where capturing DOM elements is crucial thing.

One last word

I couldn’t be more grateful for the genuine support that Mahdi has been providing, for all the of his notes and reviews. Without him, Marked would has been launched with a bad user experience on mobile devices, and several bugs.