Contributing to Open Source: Dillinger as a Case Study
If you've tried to get a job as a programmer, you must have noticed how many companies ask for GitHub profiles, active Open Source efforts and general proliferation of your work.
The more you do, the more you're worth. Unfortunately, they look at quantity over quality all too often.
But as Napoleon Hill said – if you can't do great things, do small things in a great way.
The Problem
In this article, we'll contribute to a popular open source MarkDown editor – Dillinger. Dillinger is an online text editor which lets you type MarkDown and preview it in the other part of the window at the same time, displaying it just as it will look when rendered into HTML. As the PHP editor for SitePoint, I use it quite often, but one of the gripes I have with it is the "Show HTML" function which, when selected, summons a popup with a simple div
element which contains the HTML version (in text form) of our MarkDown content. Why is this a problem? Because I then need to manually select text in a div, and if the content is long enough, I need to do so while scrolling down. Needless to say, this becomes rather tedious fast.
The Solution
The ideal solution would be adding a "Copy HTML" button which would simply place the code in the clipboard, without requiring any kind of selection. However, this would require Flash and in today's ridiculously biased anti-Flash world, this simply isn't an option. Instead, let's opt for a compromise.
We'll turn the div
containing our HTML code into a textarea that's in select-all mode by default. All anyone has to do then is just press CTRL+C, and the HTML content is in the clipboard. Even if selection/focus is lost, it's easy to select-all again just by placing the cursor inside the field and pressing CTRL+A.
Preparation
When contributing to an Open Source project, the original author often leaves hints and tips on how to do it. Sometimes it's in the README of a project, sometimes a separate file called CONTRIBUTING is made. On Dillinger, there is no such instruction (for now). Only an installation procedure is described, and that's good enough for us.
To begin, we first need to fork the project. Forking, if you're unfamiliar with the term, is the process of copying a repository to your own account in order to expand on it or make your own version. When you make a fork and add something into it, you have the option of sending a Pull Request to the original author. This makes the author know you have proposed an update to the project, and would like it included in the main repository, so that all future forks of that repository also contain your newly added feature/fix. We fork the repo, and it can now also be found here. Feel free to follow along, though by the time this article is published, the pull request this tutorial will end with will be accepted and integrated. Still, it's a good exercise in setting up Dillinger locally!
Before proceeding, please make sure you have the latest nodeJS installed.
Using your command line, cd
into the folder where you keep your projects and run the installation steps. We won't be using the plugins, so there's no need to configure those. Now, when you visit http://localhost:8080, Dillinger should be running locally.
Connecting your repo to the original
If you intend to hack on Dillinger later, you should probably set up an upstream remote to the original repo. This is done so you can frequently pull new updates from the original into your own project. Do the following in the app's folder:
git remote add --track master upstream [GIT URL HERE]
What this means in humanese is "Set up another source of code for this project at [URL] and track it's master branch". Fetching the code is done with the fetch command, when necessary (git fetch upstream
) and then you can merge it into your own project with either the merge or the rebase command:
git merge upstream/master
# or
git rebase upstream/master
Git rebase overwrites your branch's history, that's the only difference in this case, but is actually preferred by most developers due to the cleanliness of pull requests it allows – a clean history is more readable. Like this post says:
The
git rebase <upstream>
command rewinds back to a common ancestry point of<upstream>
and the current branch and replays the current branch's changes on top of the newest<upstream>
.
In other words – whenever you can, use rebase.
We don't need any of these commands for now, just keep them handy for future reference on how to update the fork of a GitHub repo.
Branching and fixing
To contribute to a project, we first need to create a new branch because
- Hacking on the master branch is just silly
- GitHub has a one pull request per branch policy
Let's do that right now.
git branch showhtml-fix
git checkout showhtml-fix
We are now in our own branch. Let's start hacking!
Open the project in your favorite code editor (I use PhpStorm since it's basically WebStorm with PHP support built in) and look at the following relevant files:
views/nav.esj
public/js/dillinger.js
.ejs
is the extension for Express view templates, and not being familiar with it shouldn't concern you.
In nav.ejs
, you'll find the following code near the top:
<div id="myModalBody" class="modal-body">
<pre>
<div id="myModalBody" class="modal-body">
</div>
</pre>
</div>
This is the modal window that shows the HTML text content when we click "Show HTML". You can find this out by yourself by clicking Inspect Element on the modal window when it shows up in Dillinger. After looking through the entire project for other uses of "#myModalBody" (PhpStorm and WebStorm have a "Find In Path" option which searches the entire project for the uses of a phrase), we conclude that its only use is Show HTML, and thus, we can safely change it. Change it to the following:
<div id="myModalBody" class="modal-body">
<textarea id="modalBodyText"></textarea>
</div>
Also, find the function showHTML
in dillinger.js
and replace the function _doneHandler
:
function _doneHandler(jqXHR, data, response){
// console.dir(resp)
var resp = JSON.parse(response.responseText)
$('#myModalBody').text(resp.data)
$('#myModal').modal()
}
with this content:
function _doneHandler(jqXHR, data, response){
var resp = JSON.parse(response.responseText)
$('#modalBodyText').val(resp.data);
$('#myModal').modal()
}
Refresh, and now we have… this?
Not quite what we were looking for, huh? That's ok, we can "Inspect Element" on the text field, and do some on-the-fly alterations until we get something we're happy with.
The first thing we do, is give the textarea a width and height of 100%.
Hm, this didn't really do what we wanted. This is because height is inherited from the parent element – 100% means 100% of the parent's height. We'll need to edit the containing div's style as well.
We'll give it an absolute position, so we can absolutely position it inside the modal window. Then, we'll give it a width of 565px, because the entire modal window is of a fixed 600px width, and 565px fits quite nicely. To stretch it from the theoretical top of the modal body, to the bottom, we use fixed values instead of percentages, making sure we only define an offset from the edges, and everything in between is filled out automatically. We give it the top value of 45px (enough to show the header of the modal, the close button and the divider line), and a bottom value of 10px, enough to simulate a nice margin. Suddenly, we have this:
If you want, add background-color: transparent
to the textarea to get rid of the red background.
Now that we know what we need to do with the CSS to get what we want, let's implement it into the project's code. Open modals.styl
(.styl is a Stylus file) and paste in the following CSS:
#myModalBody {
position: absolute;
width: 565px;
top: 45px;
bottom: 10px;
}
#modalBodyText {
width: 100%;
height: 100%;
background-color: transparent;
}
Stylus files support pure CSS, so not being familiar with Stylus is fine.
If we reload Dillinger now, our styles won't be applied. This is because the app needs to be built first – running the build.js script in the app's folder will compile all the JS and Styl files into compressed JS and CSS files. Run node build
in the root of the project, and wait for it to finish. Shouldn't take more than a couple of seconds.
Reload Dillinger now, and notice everything is just like we want it – except for one thing. The textarea isn't in select-all mode by default. Let's change that. Replace the doneHandler
function again, this time with the following:
function _doneHandler(jqXHR, data, response){
var resp = JSON.parse(response.responseText)
var textarea = $('#modalBodyText')
$(textarea).val(resp.data)
$('#myModal').on('shown.bs.modal', function (e) {
$(textarea).focus().select()
}).modal()
}
First cache the fetched textarea in order to avoid duplicate jQuery selectors. Then, we put the data inside the textarea, as we did before, but before calling Bootstrap's modal()
method on the modal window, we define an event listener which triggers when the modal appears (after all CSS transitions). When it's done, we put the focus inside the textarea and force a select-all state with the select()
method.
If you reload Dillinger now, you'll see that it works and our HTML content is automatically selected. Run node build
again to rebuild everything.
Wrapping up
While still on your own branch, commit the project with the message "Upgraded ShowHTML function to display HTML content in an auto-focused select-all textarea". A lot of files will have been changed due to the build process, but that's fine. Let's push our branch online now.
git push origin showhtml-fix
Our new branch has now appeared in the repo on GitHub.
We send a Pull Request by clicking "Compare and Pull Request". After writing a good description, and potentially linking to a related issue as seen in the screenshot below, we click "Send Pull Request".
Conclusion
That's it! With the PR sent, all that's left for us to do is wait for the author's feedback. Should our PR be accepted, we need to execute the steps in the "Connecting your repo to the original" section to fetch the latest version into our own repo.
Hopefully you've learned something new following along this contribution process – if you have any suggestions, questions, feedback or requests, please leave them in the comments below.