Posts tagged node.js

Photo URL is broken

Now that I have a substantial number of posts and some good quality content, I often find the need to look back at prior posts. Unfortunately, this has consisted of paging through my blog or clicking on tags. To resolve this, I'm introducing the search feature. To visit the search page, hover over Blog in the header, and click Search Posts.

Probably, the easiest and most effective way to implement search is to use Google Site Search, but I thought that it would be more fun to use PostgreSQL's full text search capabilities. Plus, I'd rather keep my site ad-free, and I don't want to pay. From the title picture, one can see that PostgreSQL full text search has lots of features like the ability to understand grammar, rank matches, and extract the relevant text.

Under the hood, posts are stored as tsvectors.

phillypham::DATABASE=> SELECT setweight(to_tsvector('Post title'), 'A') || setweight(to_tsvector('The_quick_brown_fox_jumps_over_the_lazy_dog'), 'B') AS tsvector FROM posts LIMIT 1;
                                     tsvector                                      
-----------------------------------------------------------------------------------
 'brown':5B 'dog':11B 'fox':6B 'jump':7B 'lazi':10B 'post':1A 'quick':4B 'titl':2A
(1 row)

As you can see, there's the ability to weight the title more heavily than the body.

Now, to make this work properly, I added another column to the posts table. I indexed it and created a trigger to make sure that it's automatically updated. Then, to integrate properly with Sequelize, I added all these queries to an afterSync hook.

=> ALTER TABLE posts ADD COLUMN title_body_tsvector tsvector;
=> UPDATE posts SET title_body_tsvector=setweight(to_tsvector('english', title), 'A') || setweight(to_tsvector('english', body), 'B');
=> CREATE INDEX IF NOT EXISTS title_body_search_idx ON posts USING gin(title_body_tsvector);
=> CREATE OR REPLACE FUNCTION posts_trigger() RETURNS trigger AS $$ begin new.title_body_tsvector := setweight(to_tsvector('english', new.title), 'A') || setweight(to_tsvector('english', new.body), 'B'); return new; end $$ LANGUAGE plpgsql;
=> CREATE TRIGGER posts_update BEFORE INSERT OR UPDATE ON posts FOR EACH ROW EXECUTE PROCEDURE posts_trigger();

See Post.js for more details.

Then, to do a search, I execute a raw query and return it as a promise. The query looks like this:

SELECT id, 
    title, 
    ts_rank_cd(title_body_tsvector,$QUERY,1) AS rank, 
    ts_headline('english', title || ' ' || body,$QUERY, 'MaxWords=100') AS headline 
    FROM posts 
    WHERE published AND title_body_tsvector @@ $QUERY
    ORDER BY rank DESC, id DESC;

The Javascript looks like this:

function(db, tsquery) {                                
    var query = tsquery.indexOf(' ') == -1 ? "to_tsquery('english','" + tsquery + "')" : "plainto_tsquery('english','" + tsquery + "')";
    return db.sequelize.query("SELECT id, title, ts_rank_cd(title_body_tsvector," + query + ",1) AS rank, ts_headline('english', title || ' ' || body," + query + ", 'MaxWords=100') AS headline FROM posts WHERE published AND title_body_tsvector @@ " + query + " ORDER BY rank DESC, id DESC",
    { model: db.Post });
}

Note the ternary operator when defining query. PostgreSQL tsquerys allow searches of arbitrary complexity with nesting and various boolean operators. Unfortunately, most people will not have the syntax memorized, so plainto_tsquery lets one search with more natural syntax. If you get the syntax wrong (e.g., forget to close a parentheses), the error will be handled gracefully thanks to promises.

Try it and let me know what you think!


I've made two updates recently to my blog. First, io.js and Node.js finally merged, and so I've upgraded to Node v4.1.1.

Also, in order to make my blog more user-friendly, I've added tags and pages. Now 5 posts are shown per page, and you can click on tags to see posts that belong to the tag. For example, you see all my posts about

by clicking on the corresponding link. The tags shown below each post are now clickable links, and if you scroll all the way to the bottom, you can find pagination buttons. I'm really starting to like writing tests in Mocha, too. It was pretty easy to make changes and ensure that nothing broke.

For those that are wondering how my life is going, recently, I'm wondering, too. I feel that I don't have much direction or purpose as of late.


Perhaps the funnest part about this blog was writing the commenting system. One may want to comment on a comment. I found a way to support this behaviour with recursion.

In the layout, using Jade mixins, we have something like

mixin commentView(comment)
    - var children = comments.filter(function(child) { return comment.id === child.commentId; })
    .wmd-preview!= comment.bodyHtml
    .children
        .inner-children
            each child in children
                +commentView(child)

We need the inner-children div to create the smooth transitions when you expand the sub-comments. Apparently, CSS transitions only work when the height is specified, so you can calculate the new height of children with inner-children.

We can also expand comments recursively with JavaScript:

function expandParent(node) {
    if (node.classList.contains('comments')) return; // we've reached the top level
    if (node.classList.contains('children')) {
        node.parentNode.querySelector('.reply-expander').classList.add('expanded');
        node.classList.add('expanded');
    }
    expandParent(node.parentNode);
}

I've left some sample comments below to show off the features. Apparently, transitions are only smooth in Chrome and Internet Explorer. Leave a comment below and try it out!


Photo URL is broken

Welcome to my new blog! After about 6 weeks, I've managed to reinvent the wheel in Node.js and have created my own blog. There are a couple reasons why I've decided to do things the hard way instead of using something like WordPress.

  • I failed to find an internship this summer, so I have ample time. I figured that it would be a good learning experience.
  • I'm very particular about my editor, so I wanted complete customization. Specifically, I wanted my Emacs keybindings that are so deeply ingrained in me.
  • Code highlighting and $\LaTeX$ with live preview were a must, too.
  • I wanted the ability to save drafts of comments and posts, either because I want to keep them private as a diary, or to write them in pieces.

Writing this blog took me far longer than anticipated, which I mainly attribute to my lack of experience. I perhaps went overboard with testing, and I wrote hundreds of unit, integration, and functional tests. I must say that using Mocha for my test driver and Selenium for my functional tests was a very pleasant experience, however. Customizing Ace with buttons and keybindings and tying it all together with MathJax and marked was not easy, either, but I'm mostly satisified to have it working to my liking. There are a few improvements to make still like adjustable height.

I probably made a mistake in sticking to a low-level framework like Express for a straight-forward create, read, update and delete (CRUD) application and pure JavaScript for much of the front end—jQuery not even once. I had a really hard time with CSS and design, too. My stylesheet is a mess and needs refactoring. Thinking about security hurt my head, too. On the plus side, I was very excited to be able to use recursion for the commenting system. I also loved being able to share server-side and client-side code. For instance, using Browserify, I can use

var Markdown = require('lib/markdown.js');

in both my server-side and client-side code. Thus, if I don't trust the client, I can do the conversion myself on the server. Also, converting Markdown server-side may reduce page loading time for the client.

All the code for the blog can be found here on GitHub. Here are some cool features of the editor, which you can try with comment box below.

Math and SVG

I'm probably the only person in the world that writes in raw SVG. If you do, too, you can draw diagrams like

a b c d e f

Now, we can prove the Pythagorean theorem. Note that $c = f + e$. By similar triangles we have that $$\frac{c}{a} = \frac{a}{f} \Rightarrow a^2 = cf,$$ and likewise, $$\frac{c}{b} = \frac{b}{e} \Rightarrow b^2 = ce.$$ Thus, \begin{align*} c^2 &= c(f+e) \\ &= cf + ce \\ &= a^2 + b^2. \end{align*}

The code for the SVG diagram is

<svg class="centered" width="230" height="130">
    <defs>
        <g id="right-angle-mark">
            <path d="M 12 0 L 12 12 L 0 12" style="stroke: black; fill: none;"></path>
        </g>
    </defs>
    <g transform="translate(15,15)">
        <g style="stroke: black">
            <line x1="0" y1="0" x2="0" y2="100"></line>
            <line x1="0" y1="0" x2="200" y2="0"></line>
            <line x1="0" y1="100" x2="200" y2="0"></line>
            <line x1="0" y1="0" x2="40" y2="80" style="stroke-dasharray: 5 5"></line>
        </g>
        <g>
            <use xlink:href="#right-angle-mark"></use>
        </g>
        <g transform="translate(40,80) rotate(243.4349)">
            <use xlink:href="#right-angle-mark"></use>
        </g>
        <g style="font-style: italic; text-anchor: middle">
            <text x="100" y="0" dy="-3">a</text>
            <text x="0" y="50" dx="-7">b</text>
            <text x="100" y="50" dx="5" dy="10">c</text>
            <text x="20" y="40" dx="7">d</text>
            <text x="20" y="90" dy="-5" dx="-3">e</text>
            <text x="120" y="40" dy="-5" dx="-3">f</text>
        </g>
    </g>
</svg>

I had to some basic trigonometry to calculate the coordinates, which mainly involved noting that $\theta = \tan^{-1}(2)$, where $\theta$ is the lower-left angle. Note that you can add a centered class to center your diagram. One of the annoying things about Mathematics Stack Exchange, from where I drew much inspiration is the lack of ability to center diagrams.

The $\LaTeX$ is $$\frac{c}{a} = \frac{a}{f} \Rightarrow a^2 = cf$$ and

\begin{align*}
c^2 &= c(f+e) \\
&= cf + ce \\
&= a^2 + b^2.
\end{align*}

Code

Code is automatically highlighted. See this implementation of binary search in C++ using iterators:

#include <iostream>     // std::cout
#include <algorithm>    // std::sort
#include <vector>       // std::vector
#include <iterator>     // ForwardIterator

namespace phillypham {
    template <class ForwardIterator, class T> ForwardIterator 
    binary_search (ForwardIterator first, ForwardIterator last, const T& val) {
        auto mid = first + (last - first)/2;        
        while (*mid != val && first < last) {        
            if (*mid > val) {
                last = mid; // new upper bound
            } else {    
                first = mid + 1; // *mid < val
            }
            mid = first + (last - first)/2;
        }
        std::cout << *mid << std::endl;
        // loop ends when val or first and last are equal
        while (mid != first && *(mid - 1) == *mid) --mid;
        return mid;
    }
}

int main (int argc, char *argv[]) {      
  std::vector<int> v{10,20,30,30,20,10,10,20}; // size 8
  std::sort (v.begin(), v.end()); // 10 10 10 20 20 20 30 30

  std::vector<int>::iterator it1, it2, it3, it4;
  it1 = phillypham::binary_search(v.begin(), v.end(), 20);   
  it2 = phillypham::binary_search(v.begin(), v.end(), -10);
  it3 = phillypham::binary_search(v.begin(), v.end(), 30); 
  it4 = phillypham::binary_search(v.begin(), v.end(), 1000); 


  std::cout << "found " << *it1 << " at position " << (it1 - v.begin()) << std::endl; 
  // 20, 3
  std::cout << "found " << *it2 << " at position " << (it2 - v.begin()) << std::endl;
  // 10, 0
  std::cout << "found " << *it3 << " at position " << (it3 - v.begin()) << std::endl; 
  // 30, 6
  std::cout << "found " << *it4 << " at position " << (it4 - v.begin()) << std::endl; 
  // undefined, 8  

  return 0;
}

Languages are auto-detected, but you can also use a hint to help hightlight.js like this

```cpp
// your code here
```

You can also insert code by highlighting your text and clicking the code button or indenting with 4 spaces.

Tables

There is support for GitHub-flavored markdown. You make this table

Item Value Qty
Computer $1600 5
Phone $12 12
Pipe $1 234

with

| Item      | Value  | Qty  |
| --------- | -----: | :--: |
| Computer  | $1600  |   5  |
| Phone     |   $12  |  12  |
| Pipe      |    $1  | 234  |