The clip board magic I learned from Typora.md

Keywords: Typora

Typora is a software I often use to write MarkDown. It is very comfortable and has a very excellent use experience:

  • Real-time Preview
  • Custom image upload service
  • Document conversion
  • Theme customization

cause

However, I encountered a very interesting thing. When I copy and paste the Typora content into the text editor, I will get the content in MarkDown format; When copied to the rich text editor, you can render rich text effects:

Copy to VS Code:

Copy to another rich text editor:

I'm curious about why there are two different results. Typora should be developed using electron (or similar technology). I try to test it with Clipboard API:

// Why use setTimeout: I tested it on the Chrome console. The clipboard relies on the page, so I need to set a 1s delay so that I can click the page focus
setTimeout(async()=>{
    const clipboardItems = await navigator.clipboard.read();
    console.log(clipboardItems)
},1000)

Then you see that there are two different types of content in the clipboard: plain text/plain and rich text text/html. Therefore, different content recipients choose different content as data. The text editor gets plain text and the rich text editor gets rich text format data.

Let's take a look at the specific contents obtained:

setTimeout(async()=>{
    const clipboardItems = await navigator.clipboard.read();
    console.log(clipboardItems)
    for (const clipboardItem of clipboardItems) {
      for (const type of clipboardItem.types) {
        const contentBlob = await clipboardItem.getType(type)
        const text = await contentBlob.text()
        console.log(text)
      }
    }
},1000)

Insert the data into the Clipboard and try:

setTimeout(async ()=>{
await navigator.clipboard.write([
      new ClipboardItem({
        ["text/plain"]: new Blob(['# Plain text and rich text '], {type:'text/plain '}),
        ["text/html"]: new Blob(['<h1 cid="n21" mdtype="heading" class="md-end-block md-heading md-focus" style="box-sizing: border-box; break-after: avoid-page; break-inside: avoid; orphans: 4; font-size: 2.25em; margin-top: 1rem; margin-bottom: 1rem; position: relative; font-weight: bold; line-height: 1.2; cursor: text; border-bottom-width: 1px; border-bottom-style: solid; border-bottom-color: rgb(238, 238, 238); white-space: pre-wrap; caret-color: rgb(51, 51, 51); color: rgb(51, 51, 51); font-family: &quot;Open Sans&quot;, &quot;Clear Sans&quot;, &quot;Helvetica Neue&quot;, Helvetica, Arial, &quot;Segoe UI Emoji&quot;, sans-serif; font-style: normal; font-variant-caps: normal; letter-spacing: normal; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration: none;"><span md-inline="plain" class="md-plain md-expand" style="box-sizing: border-box;">Plain and rich text</span></h1>'],{type:'text/html'}),
      })
    ]);
},[1000])

The results obtained by trying several rich text editors (the specific implementation of different rich text editors may be different):

  • If only plain text exists (only the plain text part in the previous code is retained), the plain text content in the clipboard is read
  • If plain text and rich text exist, the rich text content in the clipboard is read

Did Typora help us achieve this effect?

Let's take a look at the default behavior of copying rich text. Open a web page, copy the web page text, and then try using the code just now to see the content of the clipboard.

We can see that when copying rich text, the clipboard API implemented by Chrome will generate two results, one in plain text format and the other in rich text format text/html.

The difference is that when we copy in typora, we get plain text and rich text in Markdown format. Typora helps us deal with it.

Listen, copy, write to clipboard

We can use HTMLElement.oncopy to monitor replication:

Open any web page and switch to the console:

document.body.oncopy = function(e){
      console.log(e)
    var text = e.clipboardData.getData("text");
    console.log(text)
}

Copy the contents of the page, and we can see the printed results:

Originally, the data would be in the clipboardData, but we tried and didn't get the content. It seems that we can only find another way. We can get the selected content through the Range API.

document.body.oncopy = function(e){
    const selectionObj = window.getSelection()
        const rangeObj = selectionObj.getRangeAt(0)
    const fragment = rangeObj.cloneContents() // Gets the document fragment contained in the Range
    const wrapper = document.createElement('div')
    wrapper.append(fragment)
    navigator.clipboard.write([
      new ClipboardItem({
        ["text/plain"]: new Blob([wrapper.innerText,'Additional text'],{type:'text/plain'}),
        ["text/html"]: new Blob([wrapper.innerHTML,'<h1>Extra rich text</h1>'],{type:'text/html'}),
      })
    ])
}

Listening for copying can also be used to add copyright information. For example, the additional information in the above code will appear in the copied text.

You can also use document.execCommand to copy and paste content, but it is currently an deprecated API and is not recommended

Reference documents:

ClipboardItem

Clipboard-write

element.oncopy

Selection

Range

Posted by patricklcam on Thu, 18 Nov 2021 00:03:13 -0800