Plumbing into Overleaf editor logic / Rafflesia Part 2
In this article, I want to cover some of the features of the extension that involve it directly interacting with the editor.
In this short demo, I am able to generate a citation for my document using the Citations tab. The tool pulls article data from the page and parses out key information like the title, author, and other useful metadata. Then it converts that into the BibTeX format that I can use in my LaTeX document.
But as you notice, I’m doing far more than just generating some text. I also have all of the project’s
.bib files as buttons, letting you open each one. I can then copy the BibTeX text or directly add this citation to the bottom of the page.
How did I do that? I explain below.
Overleaf, like all webpages, can be inspected using browser DevTools. You can look at the elements on the page as well as the client logic. As I inspected the
window object, I started to see a handful of interesting objects.
One such object was
window._ide. That has been added by Overleaf with a lot of different managers:
outlineManager and several others.
I spent a good few hours just poking around at the various fields and methods. What’s nice about DevTools is how it shows a dropdown of all the options you can pick. This made it pretty easy to understand what was even available.
It also shows the names of parameters for methods. While the type of each parameter isn’t shown, it does give me a good place to start in understanding.
I eventually was able to come up with these functions:
But how do I call them from my extension?
The UI for my extension is technically running in an iframe outside of the page, but the same would likely be true if the UI was in a DevTools pane or some other indirect page. I cannot just call these functions directly.
So I need to post messages between my extension and the actual page. In one example, I may want to copy the generated BibTeX. From my iframe, I cannot use the clipboard APIs so I need to send my data to the webpage to copy.
I have to create an event. The format of my event is an object with both a
data. The types are all prefixed with
rafflesia so I can intercept them on the other side. The data is handled differently depending on the type.
This event needs to be received on the other side, in the webpage context. So in my
webpage-script.js, I add an event listener.
As you can see, the event receive just uses the browser clipboard APIs to write whatever data I’ve sent.
Note that the current approach could use some improvements. The postMessage documentation has additional notes on how to improve security by verifying the origin and recipient of the messages so that there aren’t errant calls.
Now that I can copy text, I can start to create even more events. For citations, I want to be able to open the expected file by its filename as well as insert text at the bottom of that file. Of course, if the user has no
.bib files currently, they should be able to create one.
The three functions can be defined as simple event senders in my Angular webpage. I can link these functions to buttons and their
Now all of these events need to be mapped to some implementation in the webpage context. They use the library of functions we’ve defined earlier.
Note how the
rafflesia_create event creates an event of its own and posts it. Our extension can have two-way interactions with the webpage.
I want to show all of the user’s
.bib files and prompt them if there are none. In order to do this, I need to know all of the files in the project. As we discovered earlier, this can be done by calling
getAllProjectFiles() on the webpage. How do we actually get this information in the iframe?
It turns out we can keep using
postMessage between any two frames within a webpage (or between a webpage and DevTools).
When the tab first loads, we add an event listener by the name
rafflesia_getProjectFiles, matching what was created by the webpage. Then, we look at the data for that event. It is an array of files including the filename.
this.mainbib becomes an array of files in our project with the
This event name can operate in both directions. When the webpage receives this event, it can execute the function and then return.
This allows us to refresh the filelist at any point.
Reading document content
A second tool reads through the entire contents of the currently open document to pull out useful comments like
FIXME that may litter your file as it’s being written. We can click the Refresh button at any point to get the latest notes and then click on any of them to jump to their position within the doc.
How is this done? A lot of it is the same as above. The Comments tool sends and receives events of type
rafflesia_read with the data being the entire contents of the doc. Once received, the
parseDocContents() is able to read through this data and pull out critical lines (those with comments like
The content parser splits up the entire draft by newline. If a given line contains of our key phrases, we track that in an array along with the line number. We need to shift the line by one as Overleaf is one-indexed.
Overleaf already has a built-in function to jump to specific lines in our document.
So our Angular webpage just needs to iterate through all of the
todos in the array and add a click listener to go to the provided line. We can also manually refresh our list.
To implement any sort of advanced actions between a browser extension and a webpage, you’ll want to post messages and use an events system. In this way, you will be able to introduce lots of well-integrated functionality.