<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Artifact]]></title><description><![CDATA[Fly me to the moon]]></description><link>https://artifact.me</link><generator>GatsbyJS</generator><lastBuildDate>Mon, 02 Mar 2026 00:36:33 GMT</lastBuildDate><item><title><![CDATA[Gatsby Plugin Azure&nbsp;Search]]></title><description><![CDATA[
<p class="wp-block-paragraph">Around a month ago I migrated this site from Hexo to Gatsby + WordPress. So far I liked the transition, especially that I can edit posts on all platforms where WordPress offers a client app. With the migration I also ported my Hexo plugin <a rel="noreferrer noopener" href="https://www.npmjs.com/package/hexo-azuresearch" target="_blank">hexo-azuresearch</a> to its Gatsby equivalent <a rel="noreferrer noopener" href="https://www.npmjs.com/package/gatsby-plugin-azure-search" target="_blank">gatsby-plugin-azure-search</a>. Both are available via npm.</p>



<p class="wp-block-paragraph">There is no difference in terms of underlying functionality. Both follows a naive approach to rebuild the index for each site build, that is, the plugin will delete the existing index then recreating it. It is done so because Azure search requires the index to be rebuilt if there are changes to existing fields, it only supports incrementally adding new fields to existing indeces.</p>



<p class="wp-block-paragraph">Check out the Github page (<a href="https://github.com/artchen/gatsby-plugin-azure-search">https://github.com/artchen/gatsby-plugin-azure-search</a>) for how to use the plugin. Pull requests are welcomed to make the it better. </p>
]]></description><link>https://artifact.me/gatsby-plugin-azure-search</link><guid isPermaLink="false">https://artifact.me/gatsby-plugin-azure-search</guid><pubDate>Sun, 14 Jun 2020 04:29:34 GMT</pubDate><content:encoded>
&lt;p class=&quot;wp-block-paragraph&quot;&gt;Around a month ago I migrated this site from Hexo to Gatsby + WordPress. So far I liked the transition, especially that I can edit posts on all platforms where WordPress offers a client app. With the migration I also ported my Hexo plugin &lt;a rel=&quot;noreferrer noopener&quot; href=&quot;https://www.npmjs.com/package/hexo-azuresearch&quot; target=&quot;_blank&quot;&gt;hexo-azuresearch&lt;/a&gt; to its Gatsby equivalent &lt;a rel=&quot;noreferrer noopener&quot; href=&quot;https://www.npmjs.com/package/gatsby-plugin-azure-search&quot; target=&quot;_blank&quot;&gt;gatsby-plugin-azure-search&lt;/a&gt;. Both are available via npm.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;There is no difference in terms of underlying functionality. Both follows a naive approach to rebuild the index for each site build, that is, the plugin will delete the existing index then recreating it. It is done so because Azure search requires the index to be rebuilt if there are changes to existing fields, it only supports incrementally adding new fields to existing indeces.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Check out the Github page (&lt;a href=&quot;https://github.com/artchen/gatsby-plugin-azure-search&quot;&gt;https://github.com/artchen/gatsby-plugin-azure-search&lt;/a&gt;) for how to use the plugin. Pull requests are welcomed to make the it better. &lt;/p&gt;
</content:encoded></item><item><title><![CDATA[Disqus Causes Unresponsive&nbsp;Page]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>In the recent project to move <a href="https://otakism.com">Otakism</a> to Gatsby and headless WordPress I discovered a pretty bad page stuck issue when integrating <a href="https://disqus.com">Disqus</a>. Most blog posts are just fine but on a particular page <a href="https://otakism.com/pixiv">Pixiv Best Artists</a> that contains much more text and links, the page will stop rendering to wait for a particular Disqus script to finish executing.</p>
<p>I looked into the issue by recording a performance profile from Chrome dev tool.</p>
<p><a href="https://cdn-artifact.otakism.com/images/2020/05/disqus-unresponsive-1.png"><img src="https://cdn-artifact.otakism.com/images/2020/05/disqus-unresponsive-1.png" alt="Performance Profile"/></a></p>
<p>The script <code>alfalfalfa.js</code> that blocked page rendering came from a Disqus domain c.disquscdn.com, which is separate from the main <code>embed.js</code> that based on my understanding renders the Disqus discussion thread. So what does this extra script do?</p>
<p>It turns out it is for targeting and attribution. In Disqus dashboard under <strong>Site</strong> &gt; <strong>Advanced</strong>, there are 2 settings for Tracking and Affiliated Links:</p>
<p><a href="https://cdn-artifact.otakism.com/images/2020/05/disqus-unresponsive-2.png"><img src="https://cdn-artifact.otakism.com/images/2020/05/disqus-unresponsive-2.png" alt="Disqus Dashboard"/></a></p>
<p>When I turn off both checkboxes, Disqus will stop loading the <code>alfalfalfa.js</code> script on to the page, which then solves the issue.</p>
<p>Taking a closer look at what these options do, I believe the Affiliate links option is the root cause. It seems to go through all <code>&lt;a&gt;</code> tags on the page in order to append the merchant code. Their script might be using a simple loop without doing it in asynchronous batches, so when the page contains a large number of links it will make the entire page unresponsive. My Pixiv archive page does have lots of links so it unfortunately became a victim.</p>
<p>In conclusion, I solved the problem for myself by unchecking the affiliated link option. This is acceptable for me &#8217;cause I don’t promote products on the site. If you do need to enable that option and you are also running into the unresponsive page issue, you might need to contact Disqus for a better solution.</p>
</div>
]]></description><link>https://artifact.me/disqus-causes-unresponsive-page</link><guid isPermaLink="false">https://artifact.me/disqus-causes-unresponsive-page</guid><pubDate>Fri, 15 May 2020 19:09:00 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;In the recent project to move &lt;a href=&quot;https://otakism.com&quot;&gt;Otakism&lt;/a&gt; to Gatsby and headless WordPress I discovered a pretty bad page stuck issue when integrating &lt;a href=&quot;https://disqus.com&quot;&gt;Disqus&lt;/a&gt;. Most blog posts are just fine but on a particular page &lt;a href=&quot;https://otakism.com/pixiv&quot;&gt;Pixiv Best Artists&lt;/a&gt; that contains much more text and links, the page will stop rendering to wait for a particular Disqus script to finish executing.&lt;/p&gt;
&lt;p&gt;I looked into the issue by recording a performance profile from Chrome dev tool.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn-artifact.otakism.com/images/2020/05/disqus-unresponsive-1.png&quot;&gt;&lt;img src=&quot;https://cdn-artifact.otakism.com/images/2020/05/disqus-unresponsive-1.png&quot; alt=&quot;Performance Profile&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The script &lt;code&gt;alfalfalfa.js&lt;/code&gt; that blocked page rendering came from a Disqus domain c.disquscdn.com, which is separate from the main &lt;code&gt;embed.js&lt;/code&gt; that based on my understanding renders the Disqus discussion thread. So what does this extra script do?&lt;/p&gt;
&lt;p&gt;It turns out it is for targeting and attribution. In Disqus dashboard under &lt;strong&gt;Site&lt;/strong&gt; &amp;gt; &lt;strong&gt;Advanced&lt;/strong&gt;, there are 2 settings for Tracking and Affiliated Links:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn-artifact.otakism.com/images/2020/05/disqus-unresponsive-2.png&quot;&gt;&lt;img src=&quot;https://cdn-artifact.otakism.com/images/2020/05/disqus-unresponsive-2.png&quot; alt=&quot;Disqus Dashboard&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When I turn off both checkboxes, Disqus will stop loading the &lt;code&gt;alfalfalfa.js&lt;/code&gt; script on to the page, which then solves the issue.&lt;/p&gt;
&lt;p&gt;Taking a closer look at what these options do, I believe the Affiliate links option is the root cause. It seems to go through all &lt;code&gt;&amp;lt;a&amp;gt;&lt;/code&gt; tags on the page in order to append the merchant code. Their script might be using a simple loop without doing it in asynchronous batches, so when the page contains a large number of links it will make the entire page unresponsive. My Pixiv archive page does have lots of links so it unfortunately became a victim.&lt;/p&gt;
&lt;p&gt;In conclusion, I solved the problem for myself by unchecking the affiliated link option. This is acceptable for me &amp;#8217;cause I don’t promote products on the site. If you do need to enable that option and you are also running into the unresponsive page issue, you might need to contact Disqus for a better solution.&lt;/p&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Headless WordPress with&nbsp;Gatsby]]></title><description><![CDATA[
<p class="wp-block-paragraph">My two blogs&nbsp;<a href="https://artifact.me/">Artifact</a>&nbsp;and&nbsp;<a href="https://otakism.com/" target="_blank" rel="noreferrer noopener">Otakism</a>&nbsp;have been on Hexo for around 5 years now. Hexo has been working great in terms of cost &#8211; I’ve hosted them for free at services like&nbsp;<a href="https://www.netlify.com/" target="_blank" rel="noreferrer noopener">Netlify</a>&nbsp;or Github pages. I also have nothing to complain about the writing experience thanks to Markdown. However, there has always been one pain point about Hexo and other static site generators alike, that is the lack of a proper content management system (CMS). To be more specific, it is hard to find and edit old posts, because they are files named with dash-separated slugs in a giant directory. Fortunately, I solved this pain recently at no cost but a bit of weekend time.</p>



<p class="wp-block-paragraph">My solution uses a private WordPress.com site as the back-end CMS, and a&nbsp;<a href="https://www.gatsbyjs.org/" target="_blank" rel="noreferrer noopener">Gatsby</a>&nbsp;generated static site sourcing data from the WordPress. This architecture is referred to as headless CMS nowadays. Certainly, it is not limited to the Gatsby WordPress combo. Technically any CMS with a content API can be used with any static generators to achieve this architecture. The difference comes down to how well it is supported on each side, so that we can build with minimum overhead.</p>



<p class="wp-block-paragraph">I chose WordPress.com as the CMS because it is free. Another popular blogging platform&nbsp;<a href="https://ghost.org/" target="_blank" rel="noreferrer noopener">Ghost</a>&nbsp;also natively supports being a headless CMS. They provided good&nbsp;<a href="https://ghost.org/docs/concepts/front-end/" target="_blank" rel="noreferrer noopener">documentation</a>&nbsp;as well. But it is much harder to host Ghost (stably) for free.</p>



<p class="wp-block-paragraph">The choice of Gatsby mainly comes to how well it supports WordPress as a headless CMS. Gatsby supports it officially with&nbsp;<a href="https://www.gatsbyjs.org/starters/GatsbyCentral/gatsby-starter-wordpress/" target="_blank" rel="noreferrer noopener">gatsby-starter-wordpress</a>&nbsp;and&nbsp;<a href="https://www.gatsbyjs.org/docs/sourcing-from-wordpress/" target="_blank" rel="noreferrer noopener">gatsby-source-wordpress</a>. The former is a boilerplate app which can also be a great reference should you wish to DIY. The latter is the official plugin that wraps WordPress REST API with&nbsp;<a href="https://graphql.org/" target="_blank" rel="noreferrer noopener">GraphQL</a>&nbsp;that Gatsby is based on.</p>



<p class="wp-block-paragraph">I don’t want to make this post a tutorial for setting up this Gatsby-Wordpress headless CMS architecture. I can’t beat the official documentation that is already very easy to read. If you want to set up something similar, just follow the links in this post. I do have a few tips that are not explicitly documented but worth sharing.</p>



<h2 class="wp-block-heading" id="Query-only-the-required-resources">Query only the required resources</h2>



<p class="wp-block-paragraph">Make sure to request only the required WordPress resources to speed up Gatsby build. It does not break anything to request all of them, but considering that Netlify free tier has a limit on monthly build time, it is wise to be more frugal.</p>



<pre class="wp-block-code javascript"><code>{
  options: {
    includedRoutes: &#091;
      `**/posts`,
      `**/pages`,
      `**/categories`,
      `**/tags`,
      `**/users`,
    ],
  }
}</code></pre>



<h2 class="wp-block-heading" id="Paginated-categories-and-tags-page">Paginated categories and tags page</h2>



<p class="wp-block-paragraph">The official gatsby-starter-wordpress has examples for categories and tags page, but those are not paginated. Here are the paginated version in <code>gatsby-node.js</code>:</p>



<pre class="wp-block-code javascript"><code>const { paginate } = require('gatsby-awesome-pagination');
const createWordpressTags = ({ graphql, actions, publishedPosts }) =&gt; {
  return graphql(`
    {
      allWordpressTag(filter: { count: { gt: 0 } }) {
        edges {
          node {
            id
            name
            slug
          }
        }
      }
    }
  `).then(result =&gt; {
    if (result.errors) {
      result.errors.forEach(e =&gt; console.error(e.toString()))
      throw new Error(result.errors);
    }

    const tagTemplate = path.resolve('./src/templates/tag.js');
    const { createPage } = actions;
    const tags = result.data.allWordpressTag.edges;

    tags.forEach((edge) =&gt; {
      const tagPath = `/tags/${edge.node.slug}`;

      paginate({
        createPage,
        items: publishedPosts.filter(postEdge =&gt; {
          if (!Array.isArray(postEdge.node.tags)) {
            return false;
          }
          for (let tag of postEdge.node.tags) {
            if (tag.slug === edge.node.slug) {
              return true;
            }
          }
        }),
        itemsPerPage: 12,
        pathPrefix: ({ pageNumber }) =&gt; {
          return pageNumber === 0 ? `${tagPath}/` : `${tagPath}/page`
        },
        component: tagTemplate,
        context: {
          name: edge.node.name,
          slug: edge.node.slug,
        },
      });
    });
  });
};</code></pre>



<p class="wp-block-paragraph">Paginated categories work in the same way.</p>



<h2 class="wp-block-heading" id="Set-up-build-hook">Set up build hook</h2>



<p class="wp-block-paragraph">Once your WordPress.com site is up and you have a Gatsby site deployed to Netlify, you might wonder how to trigger the build automatically. This can be easily set up with web hook.</p>



<p class="wp-block-paragraph">Go to the Netlify dashboard. Under&nbsp;<strong>Site Setting</strong>&nbsp;&gt;&nbsp;<strong>Build &amp; Deploy</strong>&nbsp;&gt;&nbsp;<strong>Build hooks</strong>, create a new build hook. Copy the generated url.</p>



<p class="wp-block-paragraph">Go to the traditional&nbsp;<strong>WP Admin</strong>&nbsp;of your WordPress site. Go to&nbsp;<strong>Settings</strong>&nbsp;&gt;&nbsp;<strong>Webhooks</strong>&nbsp;from the sidebar. Then click the&nbsp;<strong>Add webhook</strong>&nbsp;button. You will create 2 webhooks, one for&nbsp;<code>publish_post</code>, the other for&nbsp;<code>publish_page</code>. Enter the webhook url copied from Netlify. Choose anything from the fields list. It does not matter because Gatsby does not rely on these data to re-build the site.</p>



<p class="wp-block-paragraph">By doing so, WordPress will invoke the Netlify build hook url, and Netlify will trigger a deployment that pulls the latest data from WordPress.</p>
]]></description><link>https://artifact.me/headless-wordpress-with-gatsby</link><guid isPermaLink="false">https://artifact.me/headless-wordpress-with-gatsby</guid><pubDate>Thu, 14 May 2020 21:11:00 GMT</pubDate><content:encoded>
&lt;p class=&quot;wp-block-paragraph&quot;&gt;My two blogs&amp;nbsp;&lt;a href=&quot;https://artifact.me/&quot;&gt;Artifact&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href=&quot;https://otakism.com/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;Otakism&lt;/a&gt;&amp;nbsp;have been on Hexo for around 5 years now. Hexo has been working great in terms of cost &amp;#8211; I’ve hosted them for free at services like&amp;nbsp;&lt;a href=&quot;https://www.netlify.com/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;Netlify&lt;/a&gt;&amp;nbsp;or Github pages. I also have nothing to complain about the writing experience thanks to Markdown. However, there has always been one pain point about Hexo and other static site generators alike, that is the lack of a proper content management system (CMS). To be more specific, it is hard to find and edit old posts, because they are files named with dash-separated slugs in a giant directory. Fortunately, I solved this pain recently at no cost but a bit of weekend time.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;My solution uses a private WordPress.com site as the back-end CMS, and a&amp;nbsp;&lt;a href=&quot;https://www.gatsbyjs.org/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;Gatsby&lt;/a&gt;&amp;nbsp;generated static site sourcing data from the WordPress. This architecture is referred to as headless CMS nowadays. Certainly, it is not limited to the Gatsby WordPress combo. Technically any CMS with a content API can be used with any static generators to achieve this architecture. The difference comes down to how well it is supported on each side, so that we can build with minimum overhead.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;I chose WordPress.com as the CMS because it is free. Another popular blogging platform&amp;nbsp;&lt;a href=&quot;https://ghost.org/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;Ghost&lt;/a&gt;&amp;nbsp;also natively supports being a headless CMS. They provided good&amp;nbsp;&lt;a href=&quot;https://ghost.org/docs/concepts/front-end/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;documentation&lt;/a&gt;&amp;nbsp;as well. But it is much harder to host Ghost (stably) for free.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;The choice of Gatsby mainly comes to how well it supports WordPress as a headless CMS. Gatsby supports it officially with&amp;nbsp;&lt;a href=&quot;https://www.gatsbyjs.org/starters/GatsbyCentral/gatsby-starter-wordpress/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;gatsby-starter-wordpress&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href=&quot;https://www.gatsbyjs.org/docs/sourcing-from-wordpress/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;gatsby-source-wordpress&lt;/a&gt;. The former is a boilerplate app which can also be a great reference should you wish to DIY. The latter is the official plugin that wraps WordPress REST API with&amp;nbsp;&lt;a href=&quot;https://graphql.org/&quot; target=&quot;_blank&quot; rel=&quot;noreferrer noopener&quot;&gt;GraphQL&lt;/a&gt;&amp;nbsp;that Gatsby is based on.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;I don’t want to make this post a tutorial for setting up this Gatsby-Wordpress headless CMS architecture. I can’t beat the official documentation that is already very easy to read. If you want to set up something similar, just follow the links in this post. I do have a few tips that are not explicitly documented but worth sharing.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;Query-only-the-required-resources&quot;&gt;Query only the required resources&lt;/h2&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Make sure to request only the required WordPress resources to speed up Gatsby build. It does not break anything to request all of them, but considering that Netlify free tier has a limit on monthly build time, it is wise to be more frugal.&lt;/p&gt;



&lt;pre class=&quot;wp-block-code javascript&quot;&gt;&lt;code&gt;{
  options: {
    includedRoutes: &amp;#091;
      `**/posts`,
      `**/pages`,
      `**/categories`,
      `**/tags`,
      `**/users`,
    ],
  }
}&lt;/code&gt;&lt;/pre&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;Paginated-categories-and-tags-page&quot;&gt;Paginated categories and tags page&lt;/h2&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;The official gatsby-starter-wordpress has examples for categories and tags page, but those are not paginated. Here are the paginated version in &lt;code&gt;gatsby-node.js&lt;/code&gt;:&lt;/p&gt;



&lt;pre class=&quot;wp-block-code javascript&quot;&gt;&lt;code&gt;const { paginate } = require(&apos;gatsby-awesome-pagination&apos;);
const createWordpressTags = ({ graphql, actions, publishedPosts }) =&amp;gt; {
  return graphql(`
    {
      allWordpressTag(filter: { count: { gt: 0 } }) {
        edges {
          node {
            id
            name
            slug
          }
        }
      }
    }
  `).then(result =&amp;gt; {
    if (result.errors) {
      result.errors.forEach(e =&amp;gt; console.error(e.toString()))
      throw new Error(result.errors);
    }

    const tagTemplate = path.resolve(&apos;./src/templates/tag.js&apos;);
    const { createPage } = actions;
    const tags = result.data.allWordpressTag.edges;

    tags.forEach((edge) =&amp;gt; {
      const tagPath = `/tags/${edge.node.slug}`;

      paginate({
        createPage,
        items: publishedPosts.filter(postEdge =&amp;gt; {
          if (!Array.isArray(postEdge.node.tags)) {
            return false;
          }
          for (let tag of postEdge.node.tags) {
            if (tag.slug === edge.node.slug) {
              return true;
            }
          }
        }),
        itemsPerPage: 12,
        pathPrefix: ({ pageNumber }) =&amp;gt; {
          return pageNumber === 0 ? `${tagPath}/` : `${tagPath}/page`
        },
        component: tagTemplate,
        context: {
          name: edge.node.name,
          slug: edge.node.slug,
        },
      });
    });
  });
};&lt;/code&gt;&lt;/pre&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Paginated categories work in the same way.&lt;/p&gt;



&lt;h2 class=&quot;wp-block-heading&quot; id=&quot;Set-up-build-hook&quot;&gt;Set up build hook&lt;/h2&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Once your WordPress.com site is up and you have a Gatsby site deployed to Netlify, you might wonder how to trigger the build automatically. This can be easily set up with web hook.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Go to the Netlify dashboard. Under&amp;nbsp;&lt;strong&gt;Site Setting&lt;/strong&gt;&amp;nbsp;&amp;gt;&amp;nbsp;&lt;strong&gt;Build &amp;amp; Deploy&lt;/strong&gt;&amp;nbsp;&amp;gt;&amp;nbsp;&lt;strong&gt;Build hooks&lt;/strong&gt;, create a new build hook. Copy the generated url.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;Go to the traditional&amp;nbsp;&lt;strong&gt;WP Admin&lt;/strong&gt;&amp;nbsp;of your WordPress site. Go to&amp;nbsp;&lt;strong&gt;Settings&lt;/strong&gt;&amp;nbsp;&amp;gt;&amp;nbsp;&lt;strong&gt;Webhooks&lt;/strong&gt;&amp;nbsp;from the sidebar. Then click the&amp;nbsp;&lt;strong&gt;Add webhook&lt;/strong&gt;&amp;nbsp;button. You will create 2 webhooks, one for&amp;nbsp;&lt;code&gt;publish_post&lt;/code&gt;, the other for&amp;nbsp;&lt;code&gt;publish_page&lt;/code&gt;. Enter the webhook url copied from Netlify. Choose anything from the fields list. It does not matter because Gatsby does not rely on these data to re-build the site.&lt;/p&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;By doing so, WordPress will invoke the Netlify build hook url, and Netlify will trigger a deployment that pulls the latest data from WordPress.&lt;/p&gt;
</content:encoded></item><item><title><![CDATA[Dark Mode is&nbsp;Cool]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>I was skeptical about the “dark mode” hype but it turned out pretty darn good with this theme. Now the hesitation is whether to make dark the new default.</p>
<p>Home page:</p>
<p><a href="https://cdn-artifact.otakism.com/images/themes/dark-mode-1.jpg"><img src="https://cdn-artifact.otakism.com/images/themes/dark-mode-1.jpg" alt="Dark Mode Demo"/></a></p>
<p>Article page:</p>
<p><a href="https://cdn-artifact.otakism.com/images/themes/dark-mode-2.jpg"><img src="https://cdn-artifact.otakism.com/images/themes/dark-mode-2.jpg" alt="Dark Mode Demo"/></a></p>
<p>By the way here is the simple media query that does the magic.</p>
<pre><code class="language-css">@media (prefers-color-scheme: dark) {
  /* Your rules */
}
</code></pre>
<p>Reference: <a href="https://css-tricks.com/dark-modes-with-css/">https://css-tricks.com/dark-modes-with-css/</a></p>
</div>
]]></description><link>https://artifact.me/dark-mode-is-cool</link><guid isPermaLink="false">https://artifact.me/dark-mode-is-cool</guid><pubDate>Tue, 07 Apr 2020 23:13:00 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;I was skeptical about the “dark mode” hype but it turned out pretty darn good with this theme. Now the hesitation is whether to make dark the new default.&lt;/p&gt;
&lt;p&gt;Home page:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn-artifact.otakism.com/images/themes/dark-mode-1.jpg&quot;&gt;&lt;img src=&quot;https://cdn-artifact.otakism.com/images/themes/dark-mode-1.jpg&quot; alt=&quot;Dark Mode Demo&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Article page:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn-artifact.otakism.com/images/themes/dark-mode-2.jpg&quot;&gt;&lt;img src=&quot;https://cdn-artifact.otakism.com/images/themes/dark-mode-2.jpg&quot; alt=&quot;Dark Mode Demo&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;By the way here is the simple media query that does the magic.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-css&quot;&gt;@media (prefers-color-scheme: dark) {
  /* Your rules */
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reference: &lt;a href=&quot;https://css-tricks.com/dark-modes-with-css/&quot;&gt;https://css-tricks.com/dark-modes-with-css/&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Connect GCP App Engine to Cloud&nbsp;SQL]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>This is another quick note for my own reference. For most steps I followed this documentation: <a href="https://cloud.google.com/appengine/docs/flexible/nodejs/using-cloud-sql">Using Cloud SQL for MySQL</a>, but there are some gotchas that are worth noting.</p>
<p>Before getting into the details, here are some background information for readers. My goal is to set up a node.js application on Google Cloud <a href="https://cloud.google.com/appengine/">App Engine</a>. I use a MySQL instance on <a href="https://cloud.google.com/sql/docs/">Cloud SQL</a>, and I access my DB via <a href="http://docs.sequelizejs.com/">Sequelize</a>.</p>
<h2>Sequelize configuration</h2>
<pre><code class="language-javascript">{
  dialectOptions: {
    // socketPath: '/cloudsql/{project-id}:{region}:{cloudsql-instance-name}'
    socketPath: `/cloudsql/${process.env.CLOUD_SQL_CONNECTION_NAME}`
  }
}
</code></pre>
<p>Do not include host and port, or put host the same value as socketPath.</p>
<h2>app.yaml</h2>
<pre><code class="language-yaml">env_variables:
  CLOUD_SQL_CONNECTION_NAME: {project-id}:{region}:{cloudsql-instance-name}
beta_settings:
  cloud_sql_instances: {project-id}:{region}:{cloudsql-instance-name}
</code></pre>
<p>The <code>beta_settings</code> part can be easy to miss.</p>
<h2>Permission</h2>
<p>This part turns out unexpected, I didn&#8217;t figure it out until diving into error logs.</p>
<p>It seems both Cloud SQL API and Cloud SQL Admin API needs to be enabled. Both are not enabled by default. The documentation mentioned about Cloud SQL API, but I didn&#8217;t know Admin API is also required until actually try to deploy the app.</p>
</div>
]]></description><link>https://artifact.me/connect-gcp-app-engine-to-cloud-sql</link><guid isPermaLink="false">https://artifact.me/connect-gcp-app-engine-to-cloud-sql</guid><pubDate>Mon, 03 Jun 2019 07:12:00 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;This is another quick note for my own reference. For most steps I followed this documentation: &lt;a href=&quot;https://cloud.google.com/appengine/docs/flexible/nodejs/using-cloud-sql&quot;&gt;Using Cloud SQL for MySQL&lt;/a&gt;, but there are some gotchas that are worth noting.&lt;/p&gt;
&lt;p&gt;Before getting into the details, here are some background information for readers. My goal is to set up a node.js application on Google Cloud &lt;a href=&quot;https://cloud.google.com/appengine/&quot;&gt;App Engine&lt;/a&gt;. I use a MySQL instance on &lt;a href=&quot;https://cloud.google.com/sql/docs/&quot;&gt;Cloud SQL&lt;/a&gt;, and I access my DB via &lt;a href=&quot;http://docs.sequelizejs.com/&quot;&gt;Sequelize&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Sequelize configuration&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;{
  dialectOptions: {
    // socketPath: &apos;/cloudsql/{project-id}:{region}:{cloudsql-instance-name}&apos;
    socketPath: `/cloudsql/${process.env.CLOUD_SQL_CONNECTION_NAME}`
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not include host and port, or put host the same value as socketPath.&lt;/p&gt;
&lt;h2&gt;app.yaml&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;env_variables:
  CLOUD_SQL_CONNECTION_NAME: {project-id}:{region}:{cloudsql-instance-name}
beta_settings:
  cloud_sql_instances: {project-id}:{region}:{cloudsql-instance-name}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;beta_settings&lt;/code&gt; part can be easy to miss.&lt;/p&gt;
&lt;h2&gt;Permission&lt;/h2&gt;
&lt;p&gt;This part turns out unexpected, I didn&amp;#8217;t figure it out until diving into error logs.&lt;/p&gt;
&lt;p&gt;It seems both Cloud SQL API and Cloud SQL Admin API needs to be enabled. Both are not enabled by default. The documentation mentioned about Cloud SQL API, but I didn&amp;#8217;t know Admin API is also required until actually try to deploy the app.&lt;/p&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[WordPress with Nginx and PHP7 on AWS EC2 and&nbsp;RDS]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>I recently installed wordpress on my EC2 host. Regret a lot that I didn&#8217;t note down the steps right away. Now I have to rely on my poor goldfish memory.</p>
<h1>Install Packages</h1>
<p>Note:</p>
<ul>
<li>I use Amazon Linux.</li>
<li>I didn&#8217;t install mysql on localhost because I have an RDS instance.</li>
<li>My host already has nginx installed.</li>
</ul>
<pre><code class="language-bash">sudo yum install -y php70 php70-fpm php70-gd
sudo chkconfig php-fpm-7.0 on
</code></pre>
<h1>Configure PHP-FPM</h1>
<pre><code class="language-bash">vim /etc/php-fpm-7.0.d/www.conf
</code></pre>
<p>Make sure these are present and uncommented:</p>
<pre><code class="language-bash">user = nginx
group = nginx
listen = 127.0.0.1:9000
</code></pre>
<h1>Download WordPress</h1>
<pre><code class="language-bash">cd /var/www
wget https://wordpress.org/latest.tar.gz
tar xvf latest.tar.gz
</code></pre>
<p>Modify permissions. Grant folders 755 and files 644.</p>
<pre><code class="language-bash">sudo chown nginx:nginx -R /var/www/wordpress
sudo find /var/www/wordpress -type d -exec chmod 755 {} \;
sudo find /var/www/wordpress -type f -exec chmod 644 {} \;
</code></pre>
<h1>Configure Nginx</h1>
<p>Create a new nginx site configuration.</p>
<pre><code class="language-bash">vim /etc/nginx/sites-available/blog.otakism.com
</code></pre>
<p>With the following content.</p>
<pre><code>server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    
    server_name blog.otakism.com;
    root /var/www/wordpress;
    charset utf-8;
    
    index index.php index.html;
    
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    
    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }
    
    location ~ \.php$ {
        fastcgi_intercept_errors on;
        fastcgi_index index.php;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    
    location ~ /\. {
        deny all;
    }
    
    location ~* /(?:uploads|files)/.*\.php$ {
        deny all;
    }
    
    ssh_client_certificate /etc/nginx/ssl/cloudflare.pem;
    ssl_verify_client on;
    
    ssl on;
    ssl_certificate /etc/nginx/ssl/otakism.crt.pem;
    ssl_certificate_key /etc/nginx/ssh/otakism.key.pem;
}
</code></pre>
<p>Simlink the configuration file to <code>sites-enabled</code>.</p>
<pre><code class="language-bash">ln -s /etc/nginx/sites-available/blog.otakism.com /etc/nginx/sites-enabled/blog.otakism.com
</code></pre>
<p>Restart nginx.</p>
<pre><code class="language-bash">sudo service nginx restart
</code></pre>
<h1>Gotchas</h1>
<p>Make sure EC2 can talk to RDS. Set up the RDS security group to allow inbound TCP request to the db port from the EC2 host.</p>
</div>



<p class="wp-block-paragraph"></p>
]]></description><link>https://artifact.me/wordpress-with-nginx-and-php7-on-aws-ec2-and-rds</link><guid isPermaLink="false">https://artifact.me/wordpress-with-nginx-and-php7-on-aws-ec2-and-rds</guid><pubDate>Fri, 27 Apr 2018 16:11:39 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;I recently installed wordpress on my EC2 host. Regret a lot that I didn&amp;#8217;t note down the steps right away. Now I have to rely on my poor goldfish memory.&lt;/p&gt;
&lt;h1&gt;Install Packages&lt;/h1&gt;
&lt;p&gt;Note:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I use Amazon Linux.&lt;/li&gt;
&lt;li&gt;I didn&amp;#8217;t install mysql on localhost because I have an RDS instance.&lt;/li&gt;
&lt;li&gt;My host already has nginx installed.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo yum install -y php70 php70-fpm php70-gd
sudo chkconfig php-fpm-7.0 on
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Configure PHP-FPM&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vim /etc/php-fpm-7.0.d/www.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure these are present and uncommented:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;user = nginx
group = nginx
listen = 127.0.0.1:9000
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Download WordPress&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /var/www
wget https://wordpress.org/latest.tar.gz
tar xvf latest.tar.gz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Modify permissions. Grant folders 755 and files 644.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo chown nginx:nginx -R /var/www/wordpress
sudo find /var/www/wordpress -type d -exec chmod 755 {} \;
sudo find /var/www/wordpress -type f -exec chmod 644 {} \;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Configure Nginx&lt;/h1&gt;
&lt;p&gt;Create a new nginx site configuration.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;vim /etc/nginx/sites-available/blog.otakism.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the following content.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80;
    listen [::]:80;
    listen 443 ssl;
    listen [::]:443 ssl;
    
    server_name blog.otakism.com;
    root /var/www/wordpress;
    charset utf-8;
    
    index index.php index.html;
    
    location / {
        try_files $uri $uri/ /index.php?$args;
    }
    
    location = /favicon.ico {
        log_not_found off;
        access_log off;
    }
    
    location ~ \.php$ {
        fastcgi_intercept_errors on;
        fastcgi_index index.php;
        fastcgi_pass 127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
    
    location ~ /\. {
        deny all;
    }
    
    location ~* /(?:uploads|files)/.*\.php$ {
        deny all;
    }
    
    ssh_client_certificate /etc/nginx/ssl/cloudflare.pem;
    ssl_verify_client on;
    
    ssl on;
    ssl_certificate /etc/nginx/ssl/otakism.crt.pem;
    ssl_certificate_key /etc/nginx/ssh/otakism.key.pem;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Simlink the configuration file to &lt;code&gt;sites-enabled&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ln -s /etc/nginx/sites-available/blog.otakism.com /etc/nginx/sites-enabled/blog.otakism.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart nginx.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo service nginx restart
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Gotchas&lt;/h1&gt;
&lt;p&gt;Make sure EC2 can talk to RDS. Set up the RDS security group to allow inbound TCP request to the db port from the EC2 host.&lt;/p&gt;
&lt;/div&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;&lt;/p&gt;
</content:encoded></item><item><title><![CDATA[Install Ghost v1.x on&nbsp;Heroku]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>Heroku has always been my favorite platform for hosting prototypes and demo apps. I have 2 <a href="https://ghost.org/">Ghost</a> sites hosted on Heroku for display 2 of my Ghost themes. I haven&#8217;t touched either of them since 2016 and they have been running without a problem. However, as I checked out what has changed to Ghost since 2016, I found out the core code changes make it harder to deploy new versions (v1.0 and above) to Heroku. Fortunately there is a solution, and here is how it works.</p>
<p>Note that this post is very specific to my use case: upgrade a demo site from Ghost v0.11.x to v1.22.x. The solution will make some small changes to Ghost core code, which makes future upgrades harder to maintain. I am OK with it because I am not going to frequently update these demo sites. But if you are deploying serious projects, or a frequently updated blog, my solution is not great.</p>
<h1>Before Getting Started</h1>
<p>Make sure you have git, Node.js and npm.</p>
<p>Install Heroku CLI: <a href="https://devcenter.heroku.com/articles/heroku-cli">https://devcenter.heroku.com/articles/heroku-cli</a></p>
<h1>Download Ghost</h1>
<p>Download the <code>.zip</code> version of Ghost source code from <a href="https://ghost.org/developers/">https://ghost.org/developers/</a>. Unzip it to a folder.</p>
<h1>Initialize Git Project</h1>
<p>I highly recommend you not do the <code>git push --force</code>. I did because I really don&#8217;t care.</p>
<pre><code class="language-bash">git init
git add -A
git commit -m &quot;init project&quot;
git remote add origin &lt;your-heroku-git-url&gt;
git push --force --set-upstream origin master
</code></pre>
<h1>Set up MySQL</h1>
<p>Ghost v1+ only officially supports MySQL. So you need to deploy a MySQL add-on. Either do it via Heroku console or using this command:</p>
<pre><code class="language-bash">heroku addons:create cleardb:ignite
</code></pre>
<p>Find database connection credentials by going to Settings tab and look for the <code>CLEARDB_DATABASE_URL</code> config variable, or using this command:</p>
<pre><code class="language-bash">heroku config:get CLEARDB_DATABASE_URL
</code></pre>
<p>It should look like <code>mysql://&lt;db-user&gt;:&lt;db-password&gt;@&lt;db-host&gt;/&lt;db-ame&gt;</code>;</p>
<p>Set some more config variables:</p>
<pre><code class="language-bash">heroku config:set \ 
database__connection__user=&lt;db-user&gt; \ 
database__connection__password=&lt;db-password&gt; \ 
database__connection__host=&lt;db-host&gt; \
database__connection__database=&lt;db-name&gt; \
database__pool__max=2
</code></pre>
<p>These config variables will make sure the database connection credentials be picked up by Ghost.</p>
<p>Run the following command to initialize the database. You can also run it from Heroku console. Click the &#8216;More&#8217; dropdown and choose Run Console, type in <code>bash</code> and hit enter. You will be able to run shell commands in your dyno.</p>
<pre><code class="language-bash">heroku run &quot;knex-migrator init&quot;
</code></pre>
<p>It will fail, at least it did it to me. The error will look like <code>Error: Tarn: opt.max must be an integer &gt; 0</code>.</p>
<p>The solution is to change a core file <code>core/server/config/index.js</code>, look for this snippet:</p>
<pre><code class="language-javascript">nconf.env({
  separator: '__'
});
</code></pre>
<p>Change it to:</p>
<pre><code class="language-javascript">nconf.env({
  separator: '__',
  parseValues: true
});
</code></pre>
<p>Thanks to this Github issue: <a href="https://github.com/tgriesser/knex/issues/2570">https://github.com/tgriesser/knex/issues/2570</a></p>
<h1>Configurate Ghost</h1>
<pre><code class="language-bash">heroku config:set server__host=0.0.0.0
heroku config:set url=https://yabu.rakugaki.me
</code></pre>
<p>Add the following to <code>core/server/config/index.js</code> so that Ghost can pick up the $PORT env variable.</p>
<pre><code class="language-javascript">nconf.set('server', {
  port: process.env.PORT
});
</code></pre>
<p>Now do another deployment and the Ghost site should be running on Heroku.</p>
</div>
]]></description><link>https://artifact.me/install-ghost-v1-on-heroku</link><guid isPermaLink="false">https://artifact.me/install-ghost-v1-on-heroku</guid><pubDate>Fri, 27 Apr 2018 07:00:19 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;Heroku has always been my favorite platform for hosting prototypes and demo apps. I have 2 &lt;a href=&quot;https://ghost.org/&quot;&gt;Ghost&lt;/a&gt; sites hosted on Heroku for display 2 of my Ghost themes. I haven&amp;#8217;t touched either of them since 2016 and they have been running without a problem. However, as I checked out what has changed to Ghost since 2016, I found out the core code changes make it harder to deploy new versions (v1.0 and above) to Heroku. Fortunately there is a solution, and here is how it works.&lt;/p&gt;
&lt;p&gt;Note that this post is very specific to my use case: upgrade a demo site from Ghost v0.11.x to v1.22.x. The solution will make some small changes to Ghost core code, which makes future upgrades harder to maintain. I am OK with it because I am not going to frequently update these demo sites. But if you are deploying serious projects, or a frequently updated blog, my solution is not great.&lt;/p&gt;
&lt;h1&gt;Before Getting Started&lt;/h1&gt;
&lt;p&gt;Make sure you have git, Node.js and npm.&lt;/p&gt;
&lt;p&gt;Install Heroku CLI: &lt;a href=&quot;https://devcenter.heroku.com/articles/heroku-cli&quot;&gt;https://devcenter.heroku.com/articles/heroku-cli&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Download Ghost&lt;/h1&gt;
&lt;p&gt;Download the &lt;code&gt;.zip&lt;/code&gt; version of Ghost source code from &lt;a href=&quot;https://ghost.org/developers/&quot;&gt;https://ghost.org/developers/&lt;/a&gt;. Unzip it to a folder.&lt;/p&gt;
&lt;h1&gt;Initialize Git Project&lt;/h1&gt;
&lt;p&gt;I highly recommend you not do the &lt;code&gt;git push --force&lt;/code&gt;. I did because I really don&amp;#8217;t care.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git init
git add -A
git commit -m &amp;quot;init project&amp;quot;
git remote add origin &amp;lt;your-heroku-git-url&amp;gt;
git push --force --set-upstream origin master
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Set up MySQL&lt;/h1&gt;
&lt;p&gt;Ghost v1+ only officially supports MySQL. So you need to deploy a MySQL add-on. Either do it via Heroku console or using this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;heroku addons:create cleardb:ignite
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find database connection credentials by going to Settings tab and look for the &lt;code&gt;CLEARDB_DATABASE_URL&lt;/code&gt; config variable, or using this command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;heroku config:get CLEARDB_DATABASE_URL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It should look like &lt;code&gt;mysql://&amp;lt;db-user&amp;gt;:&amp;lt;db-password&amp;gt;@&amp;lt;db-host&amp;gt;/&amp;lt;db-ame&amp;gt;&lt;/code&gt;;&lt;/p&gt;
&lt;p&gt;Set some more config variables:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;heroku config:set \ 
database__connection__user=&amp;lt;db-user&amp;gt; \ 
database__connection__password=&amp;lt;db-password&amp;gt; \ 
database__connection__host=&amp;lt;db-host&amp;gt; \
database__connection__database=&amp;lt;db-name&amp;gt; \
database__pool__max=2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These config variables will make sure the database connection credentials be picked up by Ghost.&lt;/p&gt;
&lt;p&gt;Run the following command to initialize the database. You can also run it from Heroku console. Click the &amp;#8216;More&amp;#8217; dropdown and choose Run Console, type in &lt;code&gt;bash&lt;/code&gt; and hit enter. You will be able to run shell commands in your dyno.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;heroku run &amp;quot;knex-migrator init&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It will fail, at least it did it to me. The error will look like &lt;code&gt;Error: Tarn: opt.max must be an integer &amp;gt; 0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The solution is to change a core file &lt;code&gt;core/server/config/index.js&lt;/code&gt;, look for this snippet:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;nconf.env({
  separator: &apos;__&apos;
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change it to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;nconf.env({
  separator: &apos;__&apos;,
  parseValues: true
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thanks to this Github issue: &lt;a href=&quot;https://github.com/tgriesser/knex/issues/2570&quot;&gt;https://github.com/tgriesser/knex/issues/2570&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Configurate Ghost&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;heroku config:set server__host=0.0.0.0
heroku config:set url=https://yabu.rakugaki.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the following to &lt;code&gt;core/server/config/index.js&lt;/code&gt; so that Ghost can pick up the $PORT env variable.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;nconf.set(&apos;server&apos;, {
  port: process.env.PORT
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now do another deployment and the Ghost site should be running on Heroku.&lt;/p&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Hexo Theme Element]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p><strong>Element</strong> is another minimal theme for <a href="https://hexo.io">Hexo</a>.</p>
<h2>Dependencies</h2>
<p>This theme depends on the following Hexo plugins:</p>
<ul>
<li>hexo-generator-tag</li>
<li>hexo-generator-feed</li>
<li>hexo-renderer-ejs</li>
<li>hexo-renderer-less</li>
<li>hexo-renderer-marked</li>
<li>hexo-pagination</li>
<li>hexo-all-minifier</li>
<li>hexo-autoprefixer</li>
<li>hexo-front-matter</li>
</ul>
<p>For local search, you also need hexo-generator-json-content and add configuration to your global <code>_config.yml</code> if needed, refer to <a href="https://github.com/alexbruno/hexo-generator-json-content">hexo-generator-json-content</a> github repository.</p>
<h2>Customization</h2>
<p>Element is customizable via the <code>_config.yml</code> in the theme directory.</p>
<p>Element also depends on the global <code>_config.yml</code>. For example:</p>
<ul>
<li>Set <code>disqus_shortname</code> field to your disqus short name.</li>
<li>Set <code>theme</code> field to <code>hexo-theme-element</code>.</li>
<li>Set <code>title</code>, <code>url</code>, <code>author</code> and <code>description</code>.</li>
</ul>
<p>In addition to these settings, you may also want to edit/replace the following files:</p>
<ul>
<li>Replace the author avatar: <code>source/img/avatar.png</code>.</li>
<li>The font used on <a href="https://artifact.me">Artifact.me</a> is Futura PT. I include it via Adobe Typekit. You need to use your own web font service, or go with the default Lato via Google Font. Details in <code>layout/_partial/head.ejs</code>.</li>
</ul>
<p>This theme currently supports 4 search services:</p>
<ul>
<li>Google custom search</li>
<li>Hexo Local search (hexo-json-content)</li>
<li>Algolia search</li>
<li>Microsoft Azure search</li>
</ul>
<p>Setup guide for searching is availble here: <a href="https://github.com/artchen/universal-search">universal-search</a>.</p>
<h2>Demo</h2>
<p><a href="https://artifact.me">Artifact.me</a></p>
<h2>Copyright</h2>
<p>Public resources used in this theme:</p>
<ul>
<li><a href="https://icomoon.io/">icomoon</a></li>
<li><a href="https://necolas.github.io/normalize.css/">normalize.css</a></li>
<li><a href="https://fonts.google.com/specimen/Lato">Google Fonts &#8211; Lato</a></li>
</ul>
<p>Copyright © Art Chen</p>
<p>Please do not remove the &quot;Theme by Art Chen&quot; text and links.</p>
</div>
]]></description><link>https://artifact.me/hexo-theme-element</link><guid isPermaLink="false">https://artifact.me/hexo-theme-element</guid><pubDate>Mon, 25 Dec 2017 09:13:20 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;&lt;strong&gt;Element&lt;/strong&gt; is another minimal theme for &lt;a href=&quot;https://hexo.io&quot;&gt;Hexo&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Dependencies&lt;/h2&gt;
&lt;p&gt;This theme depends on the following Hexo plugins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hexo-generator-tag&lt;/li&gt;
&lt;li&gt;hexo-generator-feed&lt;/li&gt;
&lt;li&gt;hexo-renderer-ejs&lt;/li&gt;
&lt;li&gt;hexo-renderer-less&lt;/li&gt;
&lt;li&gt;hexo-renderer-marked&lt;/li&gt;
&lt;li&gt;hexo-pagination&lt;/li&gt;
&lt;li&gt;hexo-all-minifier&lt;/li&gt;
&lt;li&gt;hexo-autoprefixer&lt;/li&gt;
&lt;li&gt;hexo-front-matter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For local search, you also need hexo-generator-json-content and add configuration to your global &lt;code&gt;_config.yml&lt;/code&gt; if needed, refer to &lt;a href=&quot;https://github.com/alexbruno/hexo-generator-json-content&quot;&gt;hexo-generator-json-content&lt;/a&gt; github repository.&lt;/p&gt;
&lt;h2&gt;Customization&lt;/h2&gt;
&lt;p&gt;Element is customizable via the &lt;code&gt;_config.yml&lt;/code&gt; in the theme directory.&lt;/p&gt;
&lt;p&gt;Element also depends on the global &lt;code&gt;_config.yml&lt;/code&gt;. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;disqus_shortname&lt;/code&gt; field to your disqus short name.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;theme&lt;/code&gt; field to &lt;code&gt;hexo-theme-element&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;url&lt;/code&gt;, &lt;code&gt;author&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to these settings, you may also want to edit/replace the following files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace the author avatar: &lt;code&gt;source/img/avatar.png&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The font used on &lt;a href=&quot;https://artifact.me&quot;&gt;Artifact.me&lt;/a&gt; is Futura PT. I include it via Adobe Typekit. You need to use your own web font service, or go with the default Lato via Google Font. Details in &lt;code&gt;layout/_partial/head.ejs&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This theme currently supports 4 search services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Google custom search&lt;/li&gt;
&lt;li&gt;Hexo Local search (hexo-json-content)&lt;/li&gt;
&lt;li&gt;Algolia search&lt;/li&gt;
&lt;li&gt;Microsoft Azure search&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Setup guide for searching is availble here: &lt;a href=&quot;https://github.com/artchen/universal-search&quot;&gt;universal-search&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Demo&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://artifact.me&quot;&gt;Artifact.me&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Copyright&lt;/h2&gt;
&lt;p&gt;Public resources used in this theme:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://icomoon.io/&quot;&gt;icomoon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://necolas.github.io/normalize.css/&quot;&gt;normalize.css&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fonts.google.com/specimen/Lato&quot;&gt;Google Fonts &amp;#8211; Lato&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copyright © Art Chen&lt;/p&gt;
&lt;p&gt;Please do not remove the &amp;quot;Theme by Art Chen&amp;quot; text and links.&lt;/p&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Universal Search #5 Azure&nbsp;Search]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>This is the 5th article in a series that covers the steps to add searching to <a href="http://ghost.org">Ghost</a>, <a href="http://hexo.io">Hexo</a> or any general website using APIs provided by various services.</p>
<p>The latest version of Universal Search is available on Github at <a href="https://github.com/artchen/universal-search">https://github.com/artchen/universal-search</a>.</p>
<p>We have covered UI and common logics in <a href="/universal-search-1-common-logic/">the first article</a>. In this article we are going to explore <a href="https://azure.microsoft.com/en-us/services/search/">Azure Search</a>.</p>
<h1>Register for Service</h1>
<p>First of all, register an Azure account or just use your Microsoft account (outlook.com, hotmail.com, etc), and start your free trial.</p>
<p>Go to the dashboard, and create a new service.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/azure/azure-01.png"><img src="https://cdn.otakism.com/images/universal-search/azure/azure-01.png" alt="Azure-01"/></a></p>
<p>Search &quot;Azure Search&quot;.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/azure/azure-02.png"><img src="https://cdn.otakism.com/images/universal-search/azure/azure-02.png" alt="Azure-02"/></a></p>
<p>Create a free Azure Search service. The creation might take a few minutes.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/azure/azure-03.png"><img src="https://cdn.otakism.com/images/universal-search/azure/azure-03.png" alt="Azure-03"/></a></p>
<p>Go to the control panel of your new Azure Search service. Choose Keys to reveal admin keys. Note down your primary admin key. We need this admin key to access the REST API.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/azure/azure-04.png"><img src="https://cdn.otakism.com/images/universal-search/azure/azure-04.png" alt="Azure-04"/></a></p>
<p>On the same panel, select &quot;Manage query keys&quot; and create a new query key. Note it down again. This query key will be used on the front-end to query your created index.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/azure/azure-05.png"><img src="https://cdn.otakism.com/images/universal-search/azure/azure-05.png" alt="Azure-05"/></a></p>
<h1>Generate and upload data</h1>
<p>Just like <a href="https://www.algolia.com/">Algolia</a>, Azure Search does not index your website automatically. Instead, it requires the users to create indexes and upload data.</p>
<p>In this tutorial, I will use <a href="https://github.com/artchen/hexo-azuresearch">hexo-azuresearch</a>, a plugin that I wrote, to demonstrate how to integrate Azure Search with Hexo. If your website is on other platforms, Azure provides detailed documentation about all the APIs it supports so it should not be too complicated to write your own indexer and data uploader.</p>
<p>A heads up notice for non-English users, at this point I cannot get Azure Search to work with other languages. I will look into this issue.</p>
<h2>Hexo</h2>
<pre><code class="language-bash">npm install --save hexo-azuresearch
</code></pre>
<p>In your global <code>_config.yml</code>, add the following configuration:</p>
<p>In order for our front-end to work, you must include <code>title</code>, <code>excerpt:strip</code> and <code>path</code>. The other fields only help with searching accuracy.</p>
<pre><code class="language-yml"># Azure Search
AzureSearch:
  serviceURL: &quot;https://&lt;your-service-name&gt;.search.windows.net&quot;
  indexName: &quot;&lt;your-index-name&gt;&quot;
  adminKey: &quot;&lt;your-azure-search-admin-key&gt;&quot;
  analyzer: &quot;zh-Hans.lucene&quot; # optional
  fields:
    - title
    - excerpt:strip
    - content:strip
    - path
    - permalink
</code></pre>
<p>Then run the following command to index and upload data:</p>
<pre><code class="language-bash">hexo azuresearch
</code></pre>
<h2>Ghost</h2>
<p>Unfortunately there is no existing support for Ghost.</p>
<h1>Build the class</h1>
<p>This section is more of a development note. Plugin users don&#8217;t have to read it.</p>
<p>The class for Azure Search is very similar to the one we built for <a href="/universal-search-2-google-custom-search-engine/">Google Custom Search Engine</a>. The only differences are the credentials and data formats.</p>
<p><a href="https://gist.github.com/artchen/a480bf1e066ca7412a2f20c40961782d.js">https://gist.github.com/artchen/a480bf1e066ca7412a2f20c40961782d.js</a></p>
<h1>Enable your search</h1>
<pre><code class="language-javascript">var customSearch = new AzureSearch({
  serviceName: AZURE_SERVICE_NAME,
  indexName: AZURE_INDEX_NAME,
  queryKey: AZURE_QUERY_KEY
});
</code></pre>
</div>
]]></description><link>https://artifact.me/universal-search-5-azure-search</link><guid isPermaLink="false">https://artifact.me/universal-search-5-azure-search</guid><pubDate>Sat, 03 Sep 2016 22:30:47 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;This is the 5th article in a series that covers the steps to add searching to &lt;a href=&quot;http://ghost.org&quot;&gt;Ghost&lt;/a&gt;, &lt;a href=&quot;http://hexo.io&quot;&gt;Hexo&lt;/a&gt; or any general website using APIs provided by various services.&lt;/p&gt;
&lt;p&gt;The latest version of Universal Search is available on Github at &lt;a href=&quot;https://github.com/artchen/universal-search&quot;&gt;https://github.com/artchen/universal-search&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We have covered UI and common logics in &lt;a href=&quot;/universal-search-1-common-logic/&quot;&gt;the first article&lt;/a&gt;. In this article we are going to explore &lt;a href=&quot;https://azure.microsoft.com/en-us/services/search/&quot;&gt;Azure Search&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Register for Service&lt;/h1&gt;
&lt;p&gt;First of all, register an Azure account or just use your Microsoft account (outlook.com, hotmail.com, etc), and start your free trial.&lt;/p&gt;
&lt;p&gt;Go to the dashboard, and create a new service.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-01.png&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-01.png&quot; alt=&quot;Azure-01&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Search &amp;quot;Azure Search&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-02.png&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-02.png&quot; alt=&quot;Azure-02&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Create a free Azure Search service. The creation might take a few minutes.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-03.png&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-03.png&quot; alt=&quot;Azure-03&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Go to the control panel of your new Azure Search service. Choose Keys to reveal admin keys. Note down your primary admin key. We need this admin key to access the REST API.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-04.png&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-04.png&quot; alt=&quot;Azure-04&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On the same panel, select &amp;quot;Manage query keys&amp;quot; and create a new query key. Note it down again. This query key will be used on the front-end to query your created index.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-05.png&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/azure/azure-05.png&quot; alt=&quot;Azure-05&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Generate and upload data&lt;/h1&gt;
&lt;p&gt;Just like &lt;a href=&quot;https://www.algolia.com/&quot;&gt;Algolia&lt;/a&gt;, Azure Search does not index your website automatically. Instead, it requires the users to create indexes and upload data.&lt;/p&gt;
&lt;p&gt;In this tutorial, I will use &lt;a href=&quot;https://github.com/artchen/hexo-azuresearch&quot;&gt;hexo-azuresearch&lt;/a&gt;, a plugin that I wrote, to demonstrate how to integrate Azure Search with Hexo. If your website is on other platforms, Azure provides detailed documentation about all the APIs it supports so it should not be too complicated to write your own indexer and data uploader.&lt;/p&gt;
&lt;p&gt;A heads up notice for non-English users, at this point I cannot get Azure Search to work with other languages. I will look into this issue.&lt;/p&gt;
&lt;h2&gt;Hexo&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install --save hexo-azuresearch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your global &lt;code&gt;_config.yml&lt;/code&gt;, add the following configuration:&lt;/p&gt;
&lt;p&gt;In order for our front-end to work, you must include &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;excerpt:strip&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt;. The other fields only help with searching accuracy.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;# Azure Search
AzureSearch:
  serviceURL: &amp;quot;https://&amp;lt;your-service-name&amp;gt;.search.windows.net&amp;quot;
  indexName: &amp;quot;&amp;lt;your-index-name&amp;gt;&amp;quot;
  adminKey: &amp;quot;&amp;lt;your-azure-search-admin-key&amp;gt;&amp;quot;
  analyzer: &amp;quot;zh-Hans.lucene&amp;quot; # optional
  fields:
    - title
    - excerpt:strip
    - content:strip
    - path
    - permalink
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run the following command to index and upload data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hexo azuresearch
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ghost&lt;/h2&gt;
&lt;p&gt;Unfortunately there is no existing support for Ghost.&lt;/p&gt;
&lt;h1&gt;Build the class&lt;/h1&gt;
&lt;p&gt;This section is more of a development note. Plugin users don&amp;#8217;t have to read it.&lt;/p&gt;
&lt;p&gt;The class for Azure Search is very similar to the one we built for &lt;a href=&quot;/universal-search-2-google-custom-search-engine/&quot;&gt;Google Custom Search Engine&lt;/a&gt;. The only differences are the credentials and data formats.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/artchen/a480bf1e066ca7412a2f20c40961782d.js&quot;&gt;https://gist.github.com/artchen/a480bf1e066ca7412a2f20c40961782d.js&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Enable your search&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var customSearch = new AzureSearch({
  serviceName: AZURE_SERVICE_NAME,
  indexName: AZURE_INDEX_NAME,
  queryKey: AZURE_QUERY_KEY
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Universal Search #4 Algolia&nbsp;Search]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>This is the 4th article in a series that covers the steps to add searching to <a href="http://ghost.org">Ghost</a>, <a href="http://hexo.io">Hexo</a> or any general website using APIs provided by various services.</p>
<p>The latest version of Universal Search is available on Github at <a href="https://github.com/artchen/universal-search">https://github.com/artchen/universal-search</a>.</p>
<p>We have covered UI and common logics in <a href="/universal-search-1-common-logic/">the first article</a>. This time we are going to explore <a href="https://www.algolia.com/">Algolia</a>, a wonderful tool to index your website.</p>
<h1>Create an index</h1>
<p>First of all you need to register at <a href="https://www.algolia.com/">www.algolia.com</a>.</p>
<p>Algolia will guide you to create the first application, you can choose a server location based on your target users.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/algolia/algolia-01.png"><img src="https://cdn.otakism.com/images/universal-search/algolia/algolia-01.png" alt="Algolia-01"/></a></p>
<p>If a tutorial pops up, you can skip it. Go straight to create an index.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/algolia/algolia-02.png"><img src="https://cdn.otakism.com/images/universal-search/algolia/algolia-02.png" alt="Algolia-02"/></a></p>
<p>Go to API Keys and find your credentials. You will need the Application ID, the Search-only API key and the Admin API key in the following sections.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/algolia/algolia-03.png"><img src="https://cdn.otakism.com/images/universal-search/algolia/algolia-03.png" alt="Algolia-03"/></a></p>
<h1>Generate and upload data</h1>
<p>Algolia requires users to upload their search index data either manually or via provided APIs. They provide many integration sdks available at <a href="https://www.algolia.com/integrations">https://www.algolia.com/integrations</a>. Most of them are well documented.</p>
<p>If your website have back-end (database, server logics, etc), most likely Algolia already provide an server-side client. Just go ahead and follow their instructions.</p>
<p>If you are using a static site generator like Hexo, you might need to code a plugin so that every time you generate the site, the program will also upload a copy of index to Algolia. Currently the only official Algolia plugin for static site generator is Jekyll.</p>
<p>I will only cover the integration with Hexo here since it is not yet officially supported.</p>
<h2>Hexo</h2>
<p>Install and configure <a href="https://github.com/LouisBarranqueiro/hexo-algoliasearch">hexo-algoliasearch</a> in your Hexo directory. This plugin will index your site and upload selected data to Algolia.</p>
<pre><code class="language-bash">npm install --save hexo-algoliasearch
</code></pre>
<p>In your global <code>_config.yml</code>, add the following configuration:</p>
<p>In order for our front-end javascript to work, you must include <code>title</code>, <code>excerpt:strip</code> and <code>path</code>. The other fields only help with searching accuracy.</p>
<pre><code class="language-yml"># Algolia
algolia:
  appId: &quot;YOUR ALGOLIA APPLICATION ID&quot;
  apiKey: &quot;YOUR ALGOLIA SEARCH-ONLY API KEY&quot;
  adminApiKey: &quot;YOUR ALGOLIA ADMIN API KEY&quot;
  chunkSize: 5000
  indexName: &quot;YOUR INDEX NAME&quot;
  fields:
    - title
    - excerpt:strip
    - path
    - content:strip
    - permalink
</code></pre>
<p>Then run the following command to upload data:</p>
<pre><code class="language-bash">hexo algolia
</code></pre>
<h2>Ghost</h2>
<p>Unfortunately there is no existing support for Ghost.</p>
<h1>Build the class</h1>
<p>This section is more of a development note. Plugin users don&#8217;t have to read it.</p>
<p>The class for Algolia is very similar to the one we built for <a href="/universal-search-2-google-custom-search-engine/">Google Custom Search Engine</a>. The only differences are the data formats.</p>
<p>For consistency of UI, I didn&#8217;t use the javascript client provided by Algolia, which is implemented as autocomplete instant search.</p>
<p><a href="https://gist.github.com/artchen/231eee1223539cfdd094caa07bd6bec5.js">https://gist.github.com/artchen/231eee1223539cfdd094caa07bd6bec5.js</a></p>
<h1>Enable your search</h1>
<pre><code class="language-javascript">var customSearch = new AlgoliaSearch({
  apiKey: ALGOLIA_API_KEY,
  appId: ALGOLIA_APP_ID,
  indexName: ALGOLIA_INDEX_NAME
});
</code></pre>
</div>
]]></description><link>https://artifact.me/universal-search-4-algolia-search</link><guid isPermaLink="false">https://artifact.me/universal-search-4-algolia-search</guid><pubDate>Wed, 31 Aug 2016 21:55:26 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;This is the 4th article in a series that covers the steps to add searching to &lt;a href=&quot;http://ghost.org&quot;&gt;Ghost&lt;/a&gt;, &lt;a href=&quot;http://hexo.io&quot;&gt;Hexo&lt;/a&gt; or any general website using APIs provided by various services.&lt;/p&gt;
&lt;p&gt;The latest version of Universal Search is available on Github at &lt;a href=&quot;https://github.com/artchen/universal-search&quot;&gt;https://github.com/artchen/universal-search&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We have covered UI and common logics in &lt;a href=&quot;/universal-search-1-common-logic/&quot;&gt;the first article&lt;/a&gt;. This time we are going to explore &lt;a href=&quot;https://www.algolia.com/&quot;&gt;Algolia&lt;/a&gt;, a wonderful tool to index your website.&lt;/p&gt;
&lt;h1&gt;Create an index&lt;/h1&gt;
&lt;p&gt;First of all you need to register at &lt;a href=&quot;https://www.algolia.com/&quot;&gt;www.algolia.com&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Algolia will guide you to create the first application, you can choose a server location based on your target users.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/algolia/algolia-01.png&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/algolia/algolia-01.png&quot; alt=&quot;Algolia-01&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If a tutorial pops up, you can skip it. Go straight to create an index.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/algolia/algolia-02.png&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/algolia/algolia-02.png&quot; alt=&quot;Algolia-02&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Go to API Keys and find your credentials. You will need the Application ID, the Search-only API key and the Admin API key in the following sections.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/algolia/algolia-03.png&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/algolia/algolia-03.png&quot; alt=&quot;Algolia-03&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Generate and upload data&lt;/h1&gt;
&lt;p&gt;Algolia requires users to upload their search index data either manually or via provided APIs. They provide many integration sdks available at &lt;a href=&quot;https://www.algolia.com/integrations&quot;&gt;https://www.algolia.com/integrations&lt;/a&gt;. Most of them are well documented.&lt;/p&gt;
&lt;p&gt;If your website have back-end (database, server logics, etc), most likely Algolia already provide an server-side client. Just go ahead and follow their instructions.&lt;/p&gt;
&lt;p&gt;If you are using a static site generator like Hexo, you might need to code a plugin so that every time you generate the site, the program will also upload a copy of index to Algolia. Currently the only official Algolia plugin for static site generator is Jekyll.&lt;/p&gt;
&lt;p&gt;I will only cover the integration with Hexo here since it is not yet officially supported.&lt;/p&gt;
&lt;h2&gt;Hexo&lt;/h2&gt;
&lt;p&gt;Install and configure &lt;a href=&quot;https://github.com/LouisBarranqueiro/hexo-algoliasearch&quot;&gt;hexo-algoliasearch&lt;/a&gt; in your Hexo directory. This plugin will index your site and upload selected data to Algolia.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install --save hexo-algoliasearch
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your global &lt;code&gt;_config.yml&lt;/code&gt;, add the following configuration:&lt;/p&gt;
&lt;p&gt;In order for our front-end javascript to work, you must include &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;excerpt:strip&lt;/code&gt; and &lt;code&gt;path&lt;/code&gt;. The other fields only help with searching accuracy.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;# Algolia
algolia:
  appId: &amp;quot;YOUR ALGOLIA APPLICATION ID&amp;quot;
  apiKey: &amp;quot;YOUR ALGOLIA SEARCH-ONLY API KEY&amp;quot;
  adminApiKey: &amp;quot;YOUR ALGOLIA ADMIN API KEY&amp;quot;
  chunkSize: 5000
  indexName: &amp;quot;YOUR INDEX NAME&amp;quot;
  fields:
    - title
    - excerpt:strip
    - path
    - content:strip
    - permalink
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run the following command to upload data:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;hexo algolia
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Ghost&lt;/h2&gt;
&lt;p&gt;Unfortunately there is no existing support for Ghost.&lt;/p&gt;
&lt;h1&gt;Build the class&lt;/h1&gt;
&lt;p&gt;This section is more of a development note. Plugin users don&amp;#8217;t have to read it.&lt;/p&gt;
&lt;p&gt;The class for Algolia is very similar to the one we built for &lt;a href=&quot;/universal-search-2-google-custom-search-engine/&quot;&gt;Google Custom Search Engine&lt;/a&gt;. The only differences are the data formats.&lt;/p&gt;
&lt;p&gt;For consistency of UI, I didn&amp;#8217;t use the javascript client provided by Algolia, which is implemented as autocomplete instant search.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/artchen/231eee1223539cfdd094caa07bd6bec5.js&quot;&gt;https://gist.github.com/artchen/231eee1223539cfdd094caa07bd6bec5.js&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Enable your search&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var customSearch = new AlgoliaSearch({
  apiKey: ALGOLIA_API_KEY,
  appId: ALGOLIA_APP_ID,
  indexName: ALGOLIA_INDEX_NAME
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Universal Search #3 Hexo Local&nbsp;Search]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>This is the 3rd article in a series that covers the steps to add searching to <a href="http://ghost.org">Ghost</a>, <a href="http://hexo.io">Hexo</a> or any general website using APIs provided by various services.</p>
<p>The latest version of Universal Search is available on Github at <a href="https://github.com/artchen/universal-search">https://github.com/artchen/universal-search</a>.</p>
<p>We have covered UI and common logics in <a href="/universal-search-1-common-logic/">the first article</a>. This time we are going to explore Hexo local search. If you website is not generated by Hexo, you don&#8217;t necessarily need to read this article.</p>
<h1>Basic Idea</h1>
<p>Static site generator like Hexo does not rely on any database. Instead they pre-generate all pages and save them as individual html files. If we want to add search ability without using 3rd-party services, we will have to generate the &quot;database&quot; ourselves. In this case, the &quot;database&quot; is a index file that contains essential information of pages and posts. When the user submit a search request, the front-end javascript will download this index file, and searching for relevant items.</p>
<h1>Generate Index File</h1>
<p>There are several Hexo generators that does this for us. I use <a href="https://github.com/alexbruno/hexo-generator-json-content">hexo-generator-json-content</a> by <a href="https://github.com/alexbruno">alexbruno</a> for this project.</p>
<p>To install this plugin, first navigate to your Hexo directory, then</p>
<pre><code class="language-bash">npm install --save hexo-generator-json-content
</code></pre>
<p>Then edit the global <code>_config.yml</code>, add the following configuration:</p>
<pre><code class="language-yml">jsonContent:
  meta: false
  keywords: false # (english, spanish, polish, german, french, italian, dutch, russian, portuguese, swedish)
  pages:
    title: true
    slug: false
    date: false
    updated: false
    comments: false
    path: true
    link: false
    permalink: true
    excerpt: false
    keywords: false
    text: false
    raw: false
    content: true
  posts:
    title: true
    slug: false
    date: false
    updated: false
    comments: false
    path: true
    link: false
    permalink: true
    excerpt: false
    keywords: false
    text: false
    raw: false
    content: true
    categories: false
    tags: false
</code></pre>
<p>The plugin should be good to go. From now on, every time <code>hexo generate</code> is executed, a <code>content.json</code> file will be generated in the <code>public</code> directory.</p>
<h1>Build the class</h1>
<p>This section is more of a development note. Plugin users don&#8217;t have to read it.</p>
<p>The class for Hexo local search is similar to the one we created for <a href="/universal-search-2-google-custom-search-engine">Google Custom Search Engine</a> except this time we need a <code>contentSearch(post, queryText)</code> function to manually account for the keyword searching.</p>
<p>I adopted this searching algorithm from <a href="http://hahack.com/codes/local-search-engine-for-hexo/">http://hahack.com/codes/local-search-engine-for-hexo/</a>. It was pretty simple and clean.</p>
<p><a href="https://gist.github.com/artchen/dd07cb1552e0335cdda6c9f692cbca13.js">https://gist.github.com/artchen/dd07cb1552e0335cdda6c9f692cbca13.js</a></p>
<h1>Enable your search</h1>
<p>Initialize an instance of the HexoSearch class:</p>
<pre><code class="language-javascript">var customSearch = new HexoSearch({
  endpoint: &quot;&lt;path-to-your-content.json&gt;&quot; # optional
});
</code></pre>
</div>
]]></description><link>https://artifact.me/universal-search-3-hexo-local-search</link><guid isPermaLink="false">https://artifact.me/universal-search-3-hexo-local-search</guid><pubDate>Wed, 31 Aug 2016 21:54:10 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;This is the 3rd article in a series that covers the steps to add searching to &lt;a href=&quot;http://ghost.org&quot;&gt;Ghost&lt;/a&gt;, &lt;a href=&quot;http://hexo.io&quot;&gt;Hexo&lt;/a&gt; or any general website using APIs provided by various services.&lt;/p&gt;
&lt;p&gt;The latest version of Universal Search is available on Github at &lt;a href=&quot;https://github.com/artchen/universal-search&quot;&gt;https://github.com/artchen/universal-search&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We have covered UI and common logics in &lt;a href=&quot;/universal-search-1-common-logic/&quot;&gt;the first article&lt;/a&gt;. This time we are going to explore Hexo local search. If you website is not generated by Hexo, you don&amp;#8217;t necessarily need to read this article.&lt;/p&gt;
&lt;h1&gt;Basic Idea&lt;/h1&gt;
&lt;p&gt;Static site generator like Hexo does not rely on any database. Instead they pre-generate all pages and save them as individual html files. If we want to add search ability without using 3rd-party services, we will have to generate the &amp;quot;database&amp;quot; ourselves. In this case, the &amp;quot;database&amp;quot; is a index file that contains essential information of pages and posts. When the user submit a search request, the front-end javascript will download this index file, and searching for relevant items.&lt;/p&gt;
&lt;h1&gt;Generate Index File&lt;/h1&gt;
&lt;p&gt;There are several Hexo generators that does this for us. I use &lt;a href=&quot;https://github.com/alexbruno/hexo-generator-json-content&quot;&gt;hexo-generator-json-content&lt;/a&gt; by &lt;a href=&quot;https://github.com/alexbruno&quot;&gt;alexbruno&lt;/a&gt; for this project.&lt;/p&gt;
&lt;p&gt;To install this plugin, first navigate to your Hexo directory, then&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install --save hexo-generator-json-content
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then edit the global &lt;code&gt;_config.yml&lt;/code&gt;, add the following configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yml&quot;&gt;jsonContent:
  meta: false
  keywords: false # (english, spanish, polish, german, french, italian, dutch, russian, portuguese, swedish)
  pages:
    title: true
    slug: false
    date: false
    updated: false
    comments: false
    path: true
    link: false
    permalink: true
    excerpt: false
    keywords: false
    text: false
    raw: false
    content: true
  posts:
    title: true
    slug: false
    date: false
    updated: false
    comments: false
    path: true
    link: false
    permalink: true
    excerpt: false
    keywords: false
    text: false
    raw: false
    content: true
    categories: false
    tags: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The plugin should be good to go. From now on, every time &lt;code&gt;hexo generate&lt;/code&gt; is executed, a &lt;code&gt;content.json&lt;/code&gt; file will be generated in the &lt;code&gt;public&lt;/code&gt; directory.&lt;/p&gt;
&lt;h1&gt;Build the class&lt;/h1&gt;
&lt;p&gt;This section is more of a development note. Plugin users don&amp;#8217;t have to read it.&lt;/p&gt;
&lt;p&gt;The class for Hexo local search is similar to the one we created for &lt;a href=&quot;/universal-search-2-google-custom-search-engine&quot;&gt;Google Custom Search Engine&lt;/a&gt; except this time we need a &lt;code&gt;contentSearch(post, queryText)&lt;/code&gt; function to manually account for the keyword searching.&lt;/p&gt;
&lt;p&gt;I adopted this searching algorithm from &lt;a href=&quot;http://hahack.com/codes/local-search-engine-for-hexo/&quot;&gt;http://hahack.com/codes/local-search-engine-for-hexo/&lt;/a&gt;. It was pretty simple and clean.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/artchen/dd07cb1552e0335cdda6c9f692cbca13.js&quot;&gt;https://gist.github.com/artchen/dd07cb1552e0335cdda6c9f692cbca13.js&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Enable your search&lt;/h1&gt;
&lt;p&gt;Initialize an instance of the HexoSearch class:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var customSearch = new HexoSearch({
  endpoint: &amp;quot;&amp;lt;path-to-your-content.json&amp;gt;&amp;quot; # optional
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Universal Search #2 Google Custom Search&nbsp;Engine]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>This is the 2nd article in a series that covers the steps to add searching to <a href="http://ghost.org">Ghost</a>, <a href="http://hexo.io">Hexo</a> or any general website using APIs provided by various services.</p>
<p>The latest version of Universal Search is available on Github at <a href="https://github.com/artchen/universal-search">https://github.com/artchen/universal-search</a>.</p>
<p>We have covered UI and common logics in <a href="/universal-search-1-common-logic/">the previous article</a>. This time we are going to integrate <a href="https://cse.google.com">Google Custom Search Engine</a>.</p>
<h1>Get Engine ID</h1>
<p>Go to <a href="https://cse.google.com">https://cse.google.com</a>. Click the add button.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-01.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-01.jpg" alt="gcse-01"/></a></p>
<p>Go to next. Enter your website domain. Then click the create button.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-02.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-02.jpg" alt="gcse-02"/></a></p>
<p>On the congratulations screen, click Control Panel.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-03.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-03.jpg" alt="gcse-03"/></a></p>
<p>On the control panel, click Search Engine ID, note down the ID in the popup modal.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-04.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-04.jpg" alt="gcse-04"/></a></p>
<h1>Get API Key</h1>
<p>Go to <a href="https://console.developers.google.com">Google API Console</a>. You may need to register first. Click ENABLE API.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-05.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-05.jpg" alt="gcse-05"/></a></p>
<p>Search for &quot;custom search&quot;, click on the first result.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-06.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-06.jpg" alt="gcse-06"/></a></p>
<p>On the Custom Search API page, click on Credential in the left side bar.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-07.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-07.jpg" alt="gcse-07"/></a></p>
<p>On the Credential page, click create credential, then choose API Key.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-08.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-08.jpg" alt="gcse-08"/></a></p>
<p>On the popup modal, choose Browser key.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-09.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-09.jpg" alt="gcse-09"/></a></p>
<p>Name you API key and specify any URL wildcard as authorized referrers, then click create.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-10.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-10.jpg" alt="gcse-10"/></a></p>
<p>You will be prompted the API key.</p>
<p><a href="https://cdn.otakism.com/images/universal-search/google/gcse-11.jpg"><img src="https://cdn.otakism.com/images/universal-search/google/gcse-11.jpg" alt="gcse-11"/></a></p>
<h1>Build the class</h1>
<p>This section is more of a development note. Plugin users don&#8217;t have to read it.</p>
<p>The <code>GoogleCustomSearch</code> class should inherit the <code>SearchService</code> class that we built in <a href="/universal-search-0-common-logic/">the previous article</a> so that it has all the functions that control common data logics and the UI.</p>
<p>There are only 3 functions to customize:</p>
<ul>
<li><code>buildResultList(data)</code>: traverse the result array, pass url, title and digest to the common <code>buildResult()</code> function to generate HTML markups.</li>
<li><code>buildMetadata(data)</code>: generate metadata to enable pagination.</li>
<li><code>query(queryText, startIndex, callback)</code>: send Ajax GET request to the Google Custom Search endpoint to get search results.</li>
</ul>
<p>In detail, we coded a class like this:</p>
<p><a href="https://gist.github.com/artchen/212e08ff3ae2290c7b6c0dcfb32a407f.js">https://gist.github.com/artchen/212e08ff3ae2290c7b6c0dcfb32a407f.js</a></p>
<h1>Enable your search</h1>
<p>It should be as simple as initialize an instance of the GoogleCustomSearch class with your credentials:</p>
<pre><code class="language-javascript">var customSearch = new GoogleCustomSearch({
  apiKey: GOOGLE_CUSTOM_SEARCH_API_KEY,
  engineId: GOOGLE_CUSTOM_SEARCH_ENGINE_ID
});
</code></pre>
</div>



<p class="wp-block-paragraph"></p>
]]></description><link>https://artifact.me/universal-search-2-google-custom-search-engine</link><guid isPermaLink="false">https://artifact.me/universal-search-2-google-custom-search-engine</guid><pubDate>Tue, 30 Aug 2016 15:55:05 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;This is the 2nd article in a series that covers the steps to add searching to &lt;a href=&quot;http://ghost.org&quot;&gt;Ghost&lt;/a&gt;, &lt;a href=&quot;http://hexo.io&quot;&gt;Hexo&lt;/a&gt; or any general website using APIs provided by various services.&lt;/p&gt;
&lt;p&gt;The latest version of Universal Search is available on Github at &lt;a href=&quot;https://github.com/artchen/universal-search&quot;&gt;https://github.com/artchen/universal-search&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We have covered UI and common logics in &lt;a href=&quot;/universal-search-1-common-logic/&quot;&gt;the previous article&lt;/a&gt;. This time we are going to integrate &lt;a href=&quot;https://cse.google.com&quot;&gt;Google Custom Search Engine&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Get Engine ID&lt;/h1&gt;
&lt;p&gt;Go to &lt;a href=&quot;https://cse.google.com&quot;&gt;https://cse.google.com&lt;/a&gt;. Click the add button.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-01.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-01.jpg&quot; alt=&quot;gcse-01&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Go to next. Enter your website domain. Then click the create button.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-02.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-02.jpg&quot; alt=&quot;gcse-02&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On the congratulations screen, click Control Panel.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-03.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-03.jpg&quot; alt=&quot;gcse-03&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On the control panel, click Search Engine ID, note down the ID in the popup modal.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-04.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-04.jpg&quot; alt=&quot;gcse-04&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Get API Key&lt;/h1&gt;
&lt;p&gt;Go to &lt;a href=&quot;https://console.developers.google.com&quot;&gt;Google API Console&lt;/a&gt;. You may need to register first. Click ENABLE API.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-05.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-05.jpg&quot; alt=&quot;gcse-05&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Search for &amp;quot;custom search&amp;quot;, click on the first result.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-06.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-06.jpg&quot; alt=&quot;gcse-06&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On the Custom Search API page, click on Credential in the left side bar.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-07.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-07.jpg&quot; alt=&quot;gcse-07&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On the Credential page, click create credential, then choose API Key.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-08.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-08.jpg&quot; alt=&quot;gcse-08&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On the popup modal, choose Browser key.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-09.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-09.jpg&quot; alt=&quot;gcse-09&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Name you API key and specify any URL wildcard as authorized referrers, then click create.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-10.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-10.jpg&quot; alt=&quot;gcse-10&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You will be prompted the API key.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-11.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/images/universal-search/google/gcse-11.jpg&quot; alt=&quot;gcse-11&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Build the class&lt;/h1&gt;
&lt;p&gt;This section is more of a development note. Plugin users don&amp;#8217;t have to read it.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;GoogleCustomSearch&lt;/code&gt; class should inherit the &lt;code&gt;SearchService&lt;/code&gt; class that we built in &lt;a href=&quot;/universal-search-0-common-logic/&quot;&gt;the previous article&lt;/a&gt; so that it has all the functions that control common data logics and the UI.&lt;/p&gt;
&lt;p&gt;There are only 3 functions to customize:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;buildResultList(data)&lt;/code&gt;: traverse the result array, pass url, title and digest to the common &lt;code&gt;buildResult()&lt;/code&gt; function to generate HTML markups.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buildMetadata(data)&lt;/code&gt;: generate metadata to enable pagination.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query(queryText, startIndex, callback)&lt;/code&gt;: send Ajax GET request to the Google Custom Search endpoint to get search results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In detail, we coded a class like this:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/artchen/212e08ff3ae2290c7b6c0dcfb32a407f.js&quot;&gt;https://gist.github.com/artchen/212e08ff3ae2290c7b6c0dcfb32a407f.js&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Enable your search&lt;/h1&gt;
&lt;p&gt;It should be as simple as initialize an instance of the GoogleCustomSearch class with your credentials:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var customSearch = new GoogleCustomSearch({
  apiKey: GOOGLE_CUSTOM_SEARCH_API_KEY,
  engineId: GOOGLE_CUSTOM_SEARCH_ENGINE_ID
});
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;&lt;/p&gt;
</content:encoded></item><item><title><![CDATA[Universal Search #1 Common&nbsp;Logic]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>This is the 1st article in a series that covers the steps to add searching to <a href="http://ghost.org">Ghost</a>, <a href="http://hexo.io">Hexo</a> or any general website using APIs provided by various services.</p>
<p>The goal of this series is to eventually create a javascript package that allows users to add searching to their websites by only copy-pasting code snippets and changing some configurations.</p>
<p>I name the series &quot;universal search&quot; because these techniques are meant to work on any website. In reality, some of them require additional plugins to be installed, some of them may only apply to a few platforms. What I am trying to do here is to provide as many options as possible so that users can find at least one solution that works.</p>
<h1>Before reading</h1>
<p>The latest version of Universal Search is available on Github at <a href="https://github.com/artchen/universal-search">https://github.com/artchen/universal-search</a>, feel free to clone and install.</p>
<p>This article discusses my design of the plugin. If you only want to use it without knowing everything behind the scene, you can go ahead to one the of the following tutorials to setup individual search services:</p>
<ul>
<li><a href="/universal-search-2-google-custom-search-engine/">Google Custom Search Engine</a></li>
<li><a href="/universal-search-3-hexo-local-search">Hexo Local Search</a> &#8211; Hexo only</li>
<li><a href="/universal-search-4-algolia-search">Algolia Search</a></li>
<li><a href="/universal-search-5-azure-search">Azure Search</a></li>
<li><a href="/universal-search-6-baidu-search">Baidu Search</a></li>
</ul>
<h1>Basic Workflow</h1>
<ul>
<li>Register a 3rd-party searching service, get the credentials (API Key, ID, etc).</li>
<li>Send Ajax GET requests to some endpoint, get response in JSON format.</li>
<li>Parse the response if necessary.</li>
<li>Render the results on UI.</li>
</ul>
<h1>Common UI</h1>
<p>Though I always advocate original and customized design, this time I am going to use the same common UI for some consistency. This interface is independent of whatever 3rd-party search service we choose.</p>
<p><a href="https://gist.github.com/artchen/4a961c7ac9d6b9da188868c0f29a15d4.js">https://gist.github.com/artchen/4a961c7ac9d6b9da188868c0f29a15d4.js</a></p>
<p>This markup will be compressed and embedded into the javascript. When the plugin is initialized, the markup will be inserted into <code>&lt;body&gt;</code>.</p>
<h1>Common Logic</h1>
<p>The javascript part of universal search should take care of 3 main aspects:</p>
<ul>
<li>Query for search results.</li>
<li>Parse results.</li>
<li>Render UI.</li>
</ul>
<p>We already decided that the UI should be the same across services. So the following UI interactions should be defined as common functions.</p>
<ul>
<li><code>onSubmit(event)</code>: when the user submit the search form, open modal and query.</li>
<li><code>onNextPage()</code>: when the user request the next page, fetch next page.</li>
<li><code>onPrevPage()</code>: when the user request the previous page, fetch previous page.</li>
<li><code>close()</code>: when the user close the modal, fade out the modal.</li>
<li><code>uiBeforeQuery()</code>: hide the result container, show loading animation.</li>
<li><code>uiAfterQuery()</code>: hide loading animation, scroll result container to top, fade in the results.</li>
<li><code>startLoading()</code>: start the timer for loading bar animation.</li>
<li><code>stopLoading()</code>: stop the timer for loading bar animation.</li>
</ul>
<p>The query and parsing need to be defined on a per service basis because they apparently have different formats of API and response data. But we can centralize the following functions.</p>
<ul>
<li><code>onQueryError(queryText, status)</code>: empty result container, generate error messages and render in error container.</li>
<li><code>buildResult(url, title, digest)</code>: generate html for one result.</li>
</ul>
<p>Most importantly, there should be init() and destroy():</p>
<ul>
<li><code>destroy()</code>: unregister event handlers, clear markups.</li>
<li><code>init()</code>: register event handlers.</li>
</ul>
<p>All the functions listed above are common logics that goes into a super class called <code>SearchService</code>.</p>
<p>Here is the result:</p>
<p><a href="https://gist.github.com/artchen/a24102f119cf67e385db7cfa39a9c812.js">https://gist.github.com/artchen/a24102f119cf67e385db7cfa39a9c812.js</a></p>
<p>These conclude the high level design and common part of universal search.</p>
<p>You can now proceed to one of the following articles to integrate the search service of your choice:</p>
<ul>
<li><a href="/universal-search-2-google-custom-search-engine/">Google Custom Search Engine</a></li>
<li><a href="/universal-search-3-hexo-local-search">Hexo Local Search</a> &#8211; Hexo only</li>
<li><a href="/universal-search-4-algolia-search">Algolia Search</a></li>
<li><a href="/universal-search-5-azure-search">Azure Search</a></li>
<li><a href="/universal-search-6-baidu-search">Baidu Search</a></li>
</ul>
</div>
]]></description><link>https://artifact.me/universal-search-1-common-logic</link><guid isPermaLink="false">https://artifact.me/universal-search-1-common-logic</guid><pubDate>Tue, 30 Aug 2016 14:27:32 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;This is the 1st article in a series that covers the steps to add searching to &lt;a href=&quot;http://ghost.org&quot;&gt;Ghost&lt;/a&gt;, &lt;a href=&quot;http://hexo.io&quot;&gt;Hexo&lt;/a&gt; or any general website using APIs provided by various services.&lt;/p&gt;
&lt;p&gt;The goal of this series is to eventually create a javascript package that allows users to add searching to their websites by only copy-pasting code snippets and changing some configurations.&lt;/p&gt;
&lt;p&gt;I name the series &amp;quot;universal search&amp;quot; because these techniques are meant to work on any website. In reality, some of them require additional plugins to be installed, some of them may only apply to a few platforms. What I am trying to do here is to provide as many options as possible so that users can find at least one solution that works.&lt;/p&gt;
&lt;h1&gt;Before reading&lt;/h1&gt;
&lt;p&gt;The latest version of Universal Search is available on Github at &lt;a href=&quot;https://github.com/artchen/universal-search&quot;&gt;https://github.com/artchen/universal-search&lt;/a&gt;, feel free to clone and install.&lt;/p&gt;
&lt;p&gt;This article discusses my design of the plugin. If you only want to use it without knowing everything behind the scene, you can go ahead to one the of the following tutorials to setup individual search services:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-2-google-custom-search-engine/&quot;&gt;Google Custom Search Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-3-hexo-local-search&quot;&gt;Hexo Local Search&lt;/a&gt; &amp;#8211; Hexo only&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-4-algolia-search&quot;&gt;Algolia Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-5-azure-search&quot;&gt;Azure Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-6-baidu-search&quot;&gt;Baidu Search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Basic Workflow&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Register a 3rd-party searching service, get the credentials (API Key, ID, etc).&lt;/li&gt;
&lt;li&gt;Send Ajax GET requests to some endpoint, get response in JSON format.&lt;/li&gt;
&lt;li&gt;Parse the response if necessary.&lt;/li&gt;
&lt;li&gt;Render the results on UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Common UI&lt;/h1&gt;
&lt;p&gt;Though I always advocate original and customized design, this time I am going to use the same common UI for some consistency. This interface is independent of whatever 3rd-party search service we choose.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/artchen/4a961c7ac9d6b9da188868c0f29a15d4.js&quot;&gt;https://gist.github.com/artchen/4a961c7ac9d6b9da188868c0f29a15d4.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This markup will be compressed and embedded into the javascript. When the plugin is initialized, the markup will be inserted into &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Common Logic&lt;/h1&gt;
&lt;p&gt;The javascript part of universal search should take care of 3 main aspects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Query for search results.&lt;/li&gt;
&lt;li&gt;Parse results.&lt;/li&gt;
&lt;li&gt;Render UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We already decided that the UI should be the same across services. So the following UI interactions should be defined as common functions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onSubmit(event)&lt;/code&gt;: when the user submit the search form, open modal and query.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onNextPage()&lt;/code&gt;: when the user request the next page, fetch next page.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;onPrevPage()&lt;/code&gt;: when the user request the previous page, fetch previous page.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;close()&lt;/code&gt;: when the user close the modal, fade out the modal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uiBeforeQuery()&lt;/code&gt;: hide the result container, show loading animation.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uiAfterQuery()&lt;/code&gt;: hide loading animation, scroll result container to top, fade in the results.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startLoading()&lt;/code&gt;: start the timer for loading bar animation.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;stopLoading()&lt;/code&gt;: stop the timer for loading bar animation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The query and parsing need to be defined on a per service basis because they apparently have different formats of API and response data. But we can centralize the following functions.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;onQueryError(queryText, status)&lt;/code&gt;: empty result container, generate error messages and render in error container.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;buildResult(url, title, digest)&lt;/code&gt;: generate html for one result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Most importantly, there should be init() and destroy():&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;destroy()&lt;/code&gt;: unregister event handlers, clear markups.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;init()&lt;/code&gt;: register event handlers.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All the functions listed above are common logics that goes into a super class called &lt;code&gt;SearchService&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here is the result:&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://gist.github.com/artchen/a24102f119cf67e385db7cfa39a9c812.js&quot;&gt;https://gist.github.com/artchen/a24102f119cf67e385db7cfa39a9c812.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These conclude the high level design and common part of universal search.&lt;/p&gt;
&lt;p&gt;You can now proceed to one of the following articles to integrate the search service of your choice:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-2-google-custom-search-engine/&quot;&gt;Google Custom Search Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-3-hexo-local-search&quot;&gt;Hexo Local Search&lt;/a&gt; &amp;#8211; Hexo only&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-4-algolia-search&quot;&gt;Algolia Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-5-azure-search&quot;&gt;Azure Search&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/universal-search-6-baidu-search&quot;&gt;Baidu Search&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Hexo Theme Memory]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p><strong>Memory</strong> is a minimal theme for <a href="https://hexo.io">Hexo</a>.</p>
<p>The theme is available on github at <a href="https://github.com/artchen/hexo-theme-memory">https://github.com/artchen/hexo-theme-memory</a></p>
<p>This theme is also available on:</p>
<ul>
<li>Ghost version: <a href="https://github.com/artchen/ghost-theme-memory">ghost-theme-memory</a></li>
</ul>
<h1>Dependencies</h1>
<p>This theme depends on the following Hexo plugins to work correctly:</p>
<ul>
<li>hexo-generator-tag</li>
<li>hexo-renderer-ejs</li>
<li>hexo-renderer-less</li>
<li>hexo-renderer-marked</li>
<li>hexo-pagination</li>
</ul>
<p>Please make sure these plugins are installed before generate the site.</p>
<h1>Customization</h1>
<p>The theme is customizable via <code>_config.yml</code> file under the theme directory.</p>
<p>The global <code>_config.yml</code> for Hexo may also be modified. In particular:</p>
<ul>
<li>Add/modify <code>disqus_shortname</code> field, the value set to your Disqus short name</li>
<li>Change <code>theme</code> field to <code>hexo-theme-memory</code></li>
</ul>
<p>In addition to these settings, users may also want to edit/replace the following files:</p>
<ul>
<li>Replace the site logo: <code>source/images/logo.png</code>, <code>source/images/logo.psd</code></li>
<li>The search feature uses <a href="https://swiftype.com/">swiftype</a>. Please follow their instruction to setup yours.</li>
<li>The icon fonts are from <a href="https://icomoon.io/">icomoon</a>.</li>
</ul>
<h1>Demo</h1>
<p><a href="https://cdn.otakism.com/assets/hexo-theme-memory/demo/ghost-theme-memory-screenshot.jpg"><img src="https://cdn.otakism.com/assets/hexo-theme-memory/demo/ghost-theme-memory-screenshot.jpg" alt="Memory Demo"/></a></p>
<h1>Copyright</h1>
<p>Public resources used in this theme:</p>
<ul>
<li><a href="https://icomoon.io/">icomoon</a></li>
<li><a href="https://necolas.github.io/normalize.css/">normalize.css</a></li>
<li><a href="https://github.com/davatron5000/FitVids.js">fitvids.js</a></li>
</ul>
<p>Copyright © Art Chen</p>
<p>Please do not remove the &quot;Theme Memory designed by Art Chen&quot; text and links.</p>
</div>
]]></description><link>https://artifact.me/hexo-theme-memory</link><guid isPermaLink="false">https://artifact.me/hexo-theme-memory</guid><pubDate>Sat, 11 Jun 2016 18:01:36 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; is a minimal theme for &lt;a href=&quot;https://hexo.io&quot;&gt;Hexo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The theme is available on github at &lt;a href=&quot;https://github.com/artchen/hexo-theme-memory&quot;&gt;https://github.com/artchen/hexo-theme-memory&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This theme is also available on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ghost version: &lt;a href=&quot;https://github.com/artchen/ghost-theme-memory&quot;&gt;ghost-theme-memory&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Dependencies&lt;/h1&gt;
&lt;p&gt;This theme depends on the following Hexo plugins to work correctly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hexo-generator-tag&lt;/li&gt;
&lt;li&gt;hexo-renderer-ejs&lt;/li&gt;
&lt;li&gt;hexo-renderer-less&lt;/li&gt;
&lt;li&gt;hexo-renderer-marked&lt;/li&gt;
&lt;li&gt;hexo-pagination&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Please make sure these plugins are installed before generate the site.&lt;/p&gt;
&lt;h1&gt;Customization&lt;/h1&gt;
&lt;p&gt;The theme is customizable via &lt;code&gt;_config.yml&lt;/code&gt; file under the theme directory.&lt;/p&gt;
&lt;p&gt;The global &lt;code&gt;_config.yml&lt;/code&gt; for Hexo may also be modified. In particular:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add/modify &lt;code&gt;disqus_shortname&lt;/code&gt; field, the value set to your Disqus short name&lt;/li&gt;
&lt;li&gt;Change &lt;code&gt;theme&lt;/code&gt; field to &lt;code&gt;hexo-theme-memory&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In addition to these settings, users may also want to edit/replace the following files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Replace the site logo: &lt;code&gt;source/images/logo.png&lt;/code&gt;, &lt;code&gt;source/images/logo.psd&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The search feature uses &lt;a href=&quot;https://swiftype.com/&quot;&gt;swiftype&lt;/a&gt;. Please follow their instruction to setup yours.&lt;/li&gt;
&lt;li&gt;The icon fonts are from &lt;a href=&quot;https://icomoon.io/&quot;&gt;icomoon&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Demo&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/assets/hexo-theme-memory/demo/ghost-theme-memory-screenshot.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/assets/hexo-theme-memory/demo/ghost-theme-memory-screenshot.jpg&quot; alt=&quot;Memory Demo&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Copyright&lt;/h1&gt;
&lt;p&gt;Public resources used in this theme:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://icomoon.io/&quot;&gt;icomoon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://necolas.github.io/normalize.css/&quot;&gt;normalize.css&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/davatron5000/FitVids.js&quot;&gt;fitvids.js&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copyright © Art Chen&lt;/p&gt;
&lt;p&gt;Please do not remove the &amp;quot;Theme Memory designed by Art Chen&amp;quot; text and links.&lt;/p&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Ghost Theme Memory]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p><strong>Memory</strong> is a minimal theme for <a href="http://ghost.org">Ghost</a>. I am working on a <a href="https://hexo.io/">Hexo</a> version, too.</p>
<p>The theme is now available at <a href="https://github.com/artchen/ghost-theme-memory">https://github.com/artchen/ghost-theme-memory</a>.</p>
<p>This theme is also available on:</p>
<ul>
<li>Hexo version: <a href="https://github.com/artchen/hexo-theme-memory">hexo-theme-memory</a></li>
</ul>
<h1>Production</h1>
<p>If you are going to use the theme directly (without much customization).</p>
<pre><code>cd /content/themes/
git clone https://github.com/artchen/ghost-theme-memory.git
</code></pre>
<p>The version shared via Github is used on <a href="https://otakism.com">otakism.com</a>, my blog. There are quite a few things hard-coded into the template that you&#8217;ll need to customize. Including but not limited to:</p>
<ul>
<li>The site logo: <code>assets/img/logo.png</code></li>
<li>The short text under the logo: <code>partials/header.hbs</code></li>
<li>Disqus integration: <code>page.hbs</code>, <code>post.hbs</code></li>
<li>Social account links: <code>partials/footer.hbs</code></li>
</ul>
<p>The search feature relies on <a href="https://swiftype.com/">swiftype</a>. Please follow their instruction to set up your own searches.</p>
<p>The fonts are from <a href="https://typekit.com/">Typekit</a>. If you are not using typekit, please remove corresponding code is in <code>default.hbs</code>.</p>
<h1>Development</h1>
<p>Clone the repository.</p>
<pre><code>cd /content/themes/
git clone https://github.com/artchen/ghost-theme-memory.git memory
</code></pre>
<p>Install <a href="http://gulpjs.com/">gulp</a>, <a href="http://bower.io/">bower</a> and <a href="https://www.npmjs.com/">npm</a> before proceed.</p>
<p>Install and build the app:</p>
<pre><code>cd ./ghost-theme-memory
npm install
bower install
gulp
</code></pre>
<p>At this point your development environment is ready.</p>
<h1>Update</h1>
<pre><code>cd /content/themes/ghost-theme-memory/
git pull
</code></pre>
<h1>Demo</h1>
<p>Visit my blog (in Chinese) for a demo of tis theme. <a href="https://otakism.org">http://otakism.org</a>.</p>
<h1>Screenshot</h1>
<p><a href="https://cdn.otakism.com/assets/hexo-theme-memory/demo/ghost-theme-memory-screenshot.jpg"><img src="https://cdn.otakism.com/assets/hexo-theme-memory/demo/ghost-theme-memory-screenshot.jpg" alt="Memory Screenshot"/></a></p>
<h1>Copyright</h1>
<p>Public resources used in this theme:</p>
<ul>
<li><a href="https://icomoon.io/">icomoon</a></li>
<li><a href="https://necolas.github.io/normalize.css/">normalize.css</a></li>
</ul>
<p>Copyright © Art Chen</p>
<p>Please do not remove the &quot;Theme Memory designed by Art Chen&quot; text and links.</p>
</div>
]]></description><link>https://artifact.me/ghost-theme-memory</link><guid isPermaLink="false">https://artifact.me/ghost-theme-memory</guid><pubDate>Fri, 22 Apr 2016 19:49:27 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;&lt;strong&gt;Memory&lt;/strong&gt; is a minimal theme for &lt;a href=&quot;http://ghost.org&quot;&gt;Ghost&lt;/a&gt;. I am working on a &lt;a href=&quot;https://hexo.io/&quot;&gt;Hexo&lt;/a&gt; version, too.&lt;/p&gt;
&lt;p&gt;The theme is now available at &lt;a href=&quot;https://github.com/artchen/ghost-theme-memory&quot;&gt;https://github.com/artchen/ghost-theme-memory&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This theme is also available on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hexo version: &lt;a href=&quot;https://github.com/artchen/hexo-theme-memory&quot;&gt;hexo-theme-memory&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Production&lt;/h1&gt;
&lt;p&gt;If you are going to use the theme directly (without much customization).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /content/themes/
git clone https://github.com/artchen/ghost-theme-memory.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The version shared via Github is used on &lt;a href=&quot;https://otakism.com&quot;&gt;otakism.com&lt;/a&gt;, my blog. There are quite a few things hard-coded into the template that you&amp;#8217;ll need to customize. Including but not limited to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The site logo: &lt;code&gt;assets/img/logo.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The short text under the logo: &lt;code&gt;partials/header.hbs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Disqus integration: &lt;code&gt;page.hbs&lt;/code&gt;, &lt;code&gt;post.hbs&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Social account links: &lt;code&gt;partials/footer.hbs&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The search feature relies on &lt;a href=&quot;https://swiftype.com/&quot;&gt;swiftype&lt;/a&gt;. Please follow their instruction to set up your own searches.&lt;/p&gt;
&lt;p&gt;The fonts are from &lt;a href=&quot;https://typekit.com/&quot;&gt;Typekit&lt;/a&gt;. If you are not using typekit, please remove corresponding code is in &lt;code&gt;default.hbs&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Development&lt;/h1&gt;
&lt;p&gt;Clone the repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /content/themes/
git clone https://github.com/artchen/ghost-theme-memory.git memory
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install &lt;a href=&quot;http://gulpjs.com/&quot;&gt;gulp&lt;/a&gt;, &lt;a href=&quot;http://bower.io/&quot;&gt;bower&lt;/a&gt; and &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt; before proceed.&lt;/p&gt;
&lt;p&gt;Install and build the app:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ./ghost-theme-memory
npm install
bower install
gulp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point your development environment is ready.&lt;/p&gt;
&lt;h1&gt;Update&lt;/h1&gt;
&lt;pre&gt;&lt;code&gt;cd /content/themes/ghost-theme-memory/
git pull
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Demo&lt;/h1&gt;
&lt;p&gt;Visit my blog (in Chinese) for a demo of tis theme. &lt;a href=&quot;https://otakism.org&quot;&gt;http://otakism.org&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Screenshot&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn.otakism.com/assets/hexo-theme-memory/demo/ghost-theme-memory-screenshot.jpg&quot;&gt;&lt;img src=&quot;https://cdn.otakism.com/assets/hexo-theme-memory/demo/ghost-theme-memory-screenshot.jpg&quot; alt=&quot;Memory Screenshot&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Copyright&lt;/h1&gt;
&lt;p&gt;Public resources used in this theme:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://icomoon.io/&quot;&gt;icomoon&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://necolas.github.io/normalize.css/&quot;&gt;normalize.css&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Copyright © Art Chen&lt;/p&gt;
&lt;p&gt;Please do not remove the &amp;quot;Theme Memory designed by Art Chen&amp;quot; text and links.&lt;/p&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Simple Gulp Configuration for Angular&nbsp;Applications]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p><a href="http://gulpjs.com/">Gulp</a> is a great build tool for web applications. In this article I am sharing some gulp scripts that I found really useful.</p>
<h1>Installing Gulp</h1>
<p><a href="https://nodejs.org/en/">Node.js</a> needs to be installed prior to installing gulp. Once Node.js is ready on your machine, run this command to install gulp:</p>
<pre><code class="language-bash">sudo npm install --global gulp-cli
</code></pre>
<p>This will install the gulp command line tools globally, such that the <code>gulp</code> command can be invoked throughout the computer.</p>
<h1>Create a Gulp Project</h1>
<p>Change directory to the project&#8217;s root.</p>
<pre><code class="language-bash">npm init
</code></pre>
<p>First of all, initialize npm, set the project name, version, author, etc. After the setup, <code>package.json</code> will be created.</p>
<pre><code class="language-bash">npm install gulp --save-dev
</code></pre>
<p>Install gulp for the project. <code>--save-dev</code> make sure that gulp is logged in <code>package.json</code> as a development dependency. This is particularly useful when we need to port the project to another computer. With all the dependencies logged in package.json, we just run <code>npm install</code> to get them back, instead of install each of them again, or copy the gaint node_modules folder around.</p>
<h1>Project Structure</h1>
<p>For an Angular.js application with gulp as the build tool, I usually setup the project directory like this:</p>
<pre><code>|- project/                
  |- bower_components/
  |- dist/
  |- node_modules/    
  |- src/
    |- css/              # vendor css
    |- less/             # my less source code
    |- img/
    |- js/               # my javascript source code
    |- vendor/           # vendor js
    |- views/            # angular templates
    |- index.html
  |- bower.json
  |- gulpfile.js
  |- package.json
</code></pre>
<h1>A simple gulpfile.js</h1>
<p>Here comes the fun part. A basic gulpfile looks like this:</p>
<pre><code class="language-javascript">// require gulp dependencies
var gulp = require('gulp');

// declare a gulp task
gulp.task('task_name', function() {
  // task pipelines
});
</code></pre>
<p>The tasks can be run in command line like:</p>
<pre><code class="language-bash">gulp task_name
</code></pre>
<h1>Define Paths</h1>
<p>Define a path object to keep track of all the source code in the project, making it easier for reference in various gulp tasks. For one of the angularjs application, I had the following paths:</p>
<pre><code class="language-javascript">var path = {
  // template markups
  HTML: [
    'src/*.html',
    'src/views/**/*.html',
    'src/views/*.html'
  ],
  // my js source code
  JS: [
    'src/js/**/*.js'
    'src/js/*.js',
  ],
  // all less files (for changes monitoring purpose)
  LESS_ALL: [
    'src/less/*.less'
  ],
  // main less file (others are imported in style.less)
  LESS: [
    'src/less/style.less'
  ],
  // images
  IMG: [
    'src/img/**'
  ],
  // vendor css
  CSS: [
    'src/css/*.css'
  ],
  // vendor js
  VENDOR: [
    'bower_components/angular/angular.js',
    'bower_components/angular-animate/angular-aria.js',
    // ...and more
  ],
  DIST: [
    './dist'
  ]
};
</code></pre>
<h1>Installing Gulp Plugins</h1>
<p>There are tons of useful gulp plugins to explore. Gulp plugins can be installed by</p>
<pre><code class="language-bash">npm install &lt;plugin-name&gt; --save-dev
</code></pre>
<p>For example, to install <code>gulp-connect</code>:</p>
<pre><code class="language-bash">npm install gulp-connect --save-dev
</code></pre>
<h1>Useful Gulp Tasks</h1>
<p>Here I only introduce some gulp tasks that I found helpful in angularjs development.</p>
<p>Spin up a erver for <code>dist</code>.</p>
<pre><code class="language-javascript">var connect = require('gulp-connect');

gulp.task('connect', function() {
  connect.server({
    root: 'dist',
    port: 4000
  });
});
</code></pre>
<p>Clean up distribution directory.</p>
<pre><code class="language-javascript">var connect = require('gulp-clean');

gulp.task('clean', function() {
  return gulp.src('./dist/*', {force: true})
    .pipe(clean());
});
</code></pre>
<p>Use JSLint.</p>
<pre><code class="language-javascript">var jshint = require('gulp-jshint');

gulp.task('lint', function() {
  return gulp.src(path.JS)
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(jshint.reporter('fail'));
});
</code></pre>
<p>Copy over CSS and HTML</p>
<pre><code class="language-javascript">gulp.task('css', function () {
  gulp.src(path.CSS)
    .pipe(gulp.dest(path.DIST + '/css'));
});

gulp.task('html', function () {
  gulp.src(path.HTML, {base: 'src'})
    .pipe(gulp.dest(path.DIST));
});
</code></pre>
<p>Compile, minify and copy Less.</p>
<pre><code class="language-javascript">var less = require('gulp-less');
var lessDependents = require('gulp-less-dependents');
var minifyCSS = require('gulp-minify-css');

gulp.task('less', function () {
  gulp.src(path.LESS)
  	.pipe(lessDependents())
    .pipe(less())
    .pipe(minifyCSS())
    .pipe(gulp.dest(path.DIST + '/css'));
});
</code></pre>
<p>Merge, uglify and copy js files.</p>
<pre><code class="language-javascript">var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var ngAnnotate = require('gulp-ng-annotate');
var sourcemaps = require('gulp-sourcemaps');

gulp.task('js', function () {
  gulp.src(path.JS)
  	.pipe(sourcemaps.init())
		  .pipe(concat('app.js'))
		  .pipe(ngAnnotate())
		  .pipe(uglify())
		.pipe(sourcemaps.write())
    .pipe(gulp.dest(path.DIST + '/js'));
});

gulp.task('vendor', function () {
	gulp.src(path.VENDOR)
		.pipe(concat('vendor.js'))
		.pipe(ngAnnotate())
		.pipe(uglify())
    .pipe(gulp.dest(path.DIST + '/js'));
});
</code></pre>
<p>Compress images.</p>
<pre><code class="language-javascript">var imagemin = require('gulp-imagemin');

gulp.task('img', function(){
  gulp.src(path.IMG)
    .pipe(imagemin())
    .pipe(gulp.dest(path.DIST + '/img'));
});
</code></pre>
<p>Watch changes and automate tasks.</p>
<pre><code class="language-javascript">var watch = require('gulp-watch');

gulp.task('watch', function () {
  gulp.watch(path.LESS_ALL, ['less']);
  gulp.watch(path.VENDOR, ['vendor']);
  gulp.watch(path.JS, ['lint', 'js']);
  gulp.watch(path.HTML, ['html']);
  gulp.watch(path.IMG, ['img']);
});
</code></pre>
<p>Default task.</p>
<pre><code class="language-javascript">var all_tasks = ['lint', 'css', 'less', 'vendor', 'js', 'html', 'img'];

gulp.task('default', all_tasks);
</code></pre>
<h1>A More Complete gulpfile.js</h1>
<pre><code class="language-javascript">/* gulp dependencies */
var gulp = require('gulp');
var less = require('gulp-less');
var watch = require('gulp-watch');
var imagemin = require('gulp-imagemin');
var connect = require('gulp-connect');
var concat = require('gulp-concat');
var sourcemaps = require('gulp-sourcemaps');
var uglify = require('gulp-uglify');
var ngAnnotate = require('gulp-ng-annotate');
var minifyCSS = require('gulp-minify-css');
var lessDependents = require('gulp-less-dependents');
var clean = require('gulp-clean');
var bower = require('gulp-bower');
var concat_vendor = require('gulp-concat-vendor');
var jshint = require('gulp-jshint');

/* path def */
var path = {
  HTML: [
  	'src/.htaccess', 
  	'src/*.html', 
  	'src/views/**/*.html', 
  	'src/views/*.html', 
  	'src/favicon.png'
  ],
  JS: [
  	'src/js/*.js', 
  	'src/js/**/*.js'
  ],
  CSS: [
	  'src/css/*.css'
  ],
  LESS: [
  	'src/less/style.less'
  ],
  LESS_ALL: [
  	'src/less/*.less'
  ], 
  IMG: [
  	'src/img/**'
  ],
  VENDOR: [
  	'bower_components/angular/angular.js', 
		'bower_components/angular-animate/angular-animate.js', 
		'bower_components/angular-aria/angular-aria.js', 
		'bower_components/angular-messages/angular-messages.js',
		'bower_components/angular-sanitize/angular-sanitize.js',  
		'bower_components/angular-ui-router/release/angular-ui-router.js'
    // ...and more
	],
  DIST: './dist'
};

/* spin up distribution server */
gulp.task('connect', function() {
	connect.server({
		root: 'dist',
		port: 4005
	});
});

/* clean up dist dir */
gulp.task('clean', function() {
	return gulp.src('./dist/*', {force: true})
		.pipe(clean());
});

/* jslint for debugging */
gulp.task('lint', function() {
  return gulp.src(path.JS)
    .pipe(jshint())
    .pipe(jshint.reporter('default'))
    .pipe(jshint.reporter('fail'));
});

/* move css */
gulp.task('css', function () {
  gulp.src(path.CSS)
    .pipe(gulp.dest(path.DIST + '/css'));
});

/* compile less */
gulp.task('less', function () {
  gulp.src(path.LESS)
  	.pipe(lessDependents())
    .pipe(less())
    .pipe(minifyCSS())
    .pipe(gulp.dest(path.DIST + '/css'));
});

/* concat and compress app scripts */
gulp.task('js', function () {
  gulp.src(path.JS)
  	.pipe(sourcemaps.init())
		  .pipe(concat('app.js'))
		  .pipe(ngAnnotate())
		  .pipe(uglify())
		.pipe(sourcemaps.write())
    .pipe(gulp.dest(path.DIST + '/js'));
});

/* concat vendor dependencies */
gulp.task('vendor', function () {
	gulp.src(path.VENDOR)
		.pipe(concat('vendor.js'))
		.pipe(ngAnnotate())
		.pipe(uglify())
    .pipe(gulp.dest(path.DIST + '/js'));
});

/* copy over markups */
gulp.task('html', function(){
  gulp.src(path.HTML, {base: 'src'})
    .pipe(gulp.dest(path.DIST));
});

/* compress images */
gulp.task('img', function(){
  gulp.src(path.IMG)
    .pipe(imagemin())
    .pipe(gulp.dest(path.DIST + '/img'));
});

/* watch all changes */
gulp.task('watch', function () {
  gulp.watch(path.LESS_ALL, ['less']);
  gulp.watch(path.VENDOR, ['vendor']);
  gulp.watch(path.JS, ['lint', 'js']);
  gulp.watch(path.HTML, ['html']);
  gulp.watch(path.IMG, ['img']);
});

/* defualt */
gulp.task('default', all_tasks);

</code></pre>
</div>
]]></description><link>https://artifact.me/simple-gulp-configuration-for-angular-applications</link><guid isPermaLink="false">https://artifact.me/simple-gulp-configuration-for-angular-applications</guid><pubDate>Fri, 11 Mar 2016 20:54:11 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;&lt;a href=&quot;http://gulpjs.com/&quot;&gt;Gulp&lt;/a&gt; is a great build tool for web applications. In this article I am sharing some gulp scripts that I found really useful.&lt;/p&gt;
&lt;h1&gt;Installing Gulp&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://nodejs.org/en/&quot;&gt;Node.js&lt;/a&gt; needs to be installed prior to installing gulp. Once Node.js is ready on your machine, run this command to install gulp:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo npm install --global gulp-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will install the gulp command line tools globally, such that the &lt;code&gt;gulp&lt;/code&gt; command can be invoked throughout the computer.&lt;/p&gt;
&lt;h1&gt;Create a Gulp Project&lt;/h1&gt;
&lt;p&gt;Change directory to the project&amp;#8217;s root.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;First of all, initialize npm, set the project name, version, author, etc. After the setup, &lt;code&gt;package.json&lt;/code&gt; will be created.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install gulp --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install gulp for the project. &lt;code&gt;--save-dev&lt;/code&gt; make sure that gulp is logged in &lt;code&gt;package.json&lt;/code&gt; as a development dependency. This is particularly useful when we need to port the project to another computer. With all the dependencies logged in package.json, we just run &lt;code&gt;npm install&lt;/code&gt; to get them back, instead of install each of them again, or copy the gaint node_modules folder around.&lt;/p&gt;
&lt;h1&gt;Project Structure&lt;/h1&gt;
&lt;p&gt;For an Angular.js application with gulp as the build tool, I usually setup the project directory like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;|- project/                
  |- bower_components/
  |- dist/
  |- node_modules/    
  |- src/
    |- css/              # vendor css
    |- less/             # my less source code
    |- img/
    |- js/               # my javascript source code
    |- vendor/           # vendor js
    |- views/            # angular templates
    |- index.html
  |- bower.json
  |- gulpfile.js
  |- package.json
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;A simple gulpfile.js&lt;/h1&gt;
&lt;p&gt;Here comes the fun part. A basic gulpfile looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;// require gulp dependencies
var gulp = require(&apos;gulp&apos;);

// declare a gulp task
gulp.task(&apos;task_name&apos;, function() {
  // task pipelines
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The tasks can be run in command line like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gulp task_name
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Define Paths&lt;/h1&gt;
&lt;p&gt;Define a path object to keep track of all the source code in the project, making it easier for reference in various gulp tasks. For one of the angularjs application, I had the following paths:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var path = {
  // template markups
  HTML: [
    &apos;src/*.html&apos;,
    &apos;src/views/**/*.html&apos;,
    &apos;src/views/*.html&apos;
  ],
  // my js source code
  JS: [
    &apos;src/js/**/*.js&apos;
    &apos;src/js/*.js&apos;,
  ],
  // all less files (for changes monitoring purpose)
  LESS_ALL: [
    &apos;src/less/*.less&apos;
  ],
  // main less file (others are imported in style.less)
  LESS: [
    &apos;src/less/style.less&apos;
  ],
  // images
  IMG: [
    &apos;src/img/**&apos;
  ],
  // vendor css
  CSS: [
    &apos;src/css/*.css&apos;
  ],
  // vendor js
  VENDOR: [
    &apos;bower_components/angular/angular.js&apos;,
    &apos;bower_components/angular-animate/angular-aria.js&apos;,
    // ...and more
  ],
  DIST: [
    &apos;./dist&apos;
  ]
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Installing Gulp Plugins&lt;/h1&gt;
&lt;p&gt;There are tons of useful gulp plugins to explore. Gulp plugins can be installed by&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install &amp;lt;plugin-name&amp;gt; --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, to install &lt;code&gt;gulp-connect&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install gulp-connect --save-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Useful Gulp Tasks&lt;/h1&gt;
&lt;p&gt;Here I only introduce some gulp tasks that I found helpful in angularjs development.&lt;/p&gt;
&lt;p&gt;Spin up a erver for &lt;code&gt;dist&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var connect = require(&apos;gulp-connect&apos;);

gulp.task(&apos;connect&apos;, function() {
  connect.server({
    root: &apos;dist&apos;,
    port: 4000
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Clean up distribution directory.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var connect = require(&apos;gulp-clean&apos;);

gulp.task(&apos;clean&apos;, function() {
  return gulp.src(&apos;./dist/*&apos;, {force: true})
    .pipe(clean());
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use JSLint.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var jshint = require(&apos;gulp-jshint&apos;);

gulp.task(&apos;lint&apos;, function() {
  return gulp.src(path.JS)
    .pipe(jshint())
    .pipe(jshint.reporter(&apos;default&apos;))
    .pipe(jshint.reporter(&apos;fail&apos;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy over CSS and HTML&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;gulp.task(&apos;css&apos;, function () {
  gulp.src(path.CSS)
    .pipe(gulp.dest(path.DIST + &apos;/css&apos;));
});

gulp.task(&apos;html&apos;, function () {
  gulp.src(path.HTML, {base: &apos;src&apos;})
    .pipe(gulp.dest(path.DIST));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compile, minify and copy Less.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var less = require(&apos;gulp-less&apos;);
var lessDependents = require(&apos;gulp-less-dependents&apos;);
var minifyCSS = require(&apos;gulp-minify-css&apos;);

gulp.task(&apos;less&apos;, function () {
  gulp.src(path.LESS)
  	.pipe(lessDependents())
    .pipe(less())
    .pipe(minifyCSS())
    .pipe(gulp.dest(path.DIST + &apos;/css&apos;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Merge, uglify and copy js files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var concat = require(&apos;gulp-concat&apos;);
var uglify = require(&apos;gulp-uglify&apos;);
var ngAnnotate = require(&apos;gulp-ng-annotate&apos;);
var sourcemaps = require(&apos;gulp-sourcemaps&apos;);

gulp.task(&apos;js&apos;, function () {
  gulp.src(path.JS)
  	.pipe(sourcemaps.init())
		  .pipe(concat(&apos;app.js&apos;))
		  .pipe(ngAnnotate())
		  .pipe(uglify())
		.pipe(sourcemaps.write())
    .pipe(gulp.dest(path.DIST + &apos;/js&apos;));
});

gulp.task(&apos;vendor&apos;, function () {
	gulp.src(path.VENDOR)
		.pipe(concat(&apos;vendor.js&apos;))
		.pipe(ngAnnotate())
		.pipe(uglify())
    .pipe(gulp.dest(path.DIST + &apos;/js&apos;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compress images.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var imagemin = require(&apos;gulp-imagemin&apos;);

gulp.task(&apos;img&apos;, function(){
  gulp.src(path.IMG)
    .pipe(imagemin())
    .pipe(gulp.dest(path.DIST + &apos;/img&apos;));
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Watch changes and automate tasks.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var watch = require(&apos;gulp-watch&apos;);

gulp.task(&apos;watch&apos;, function () {
  gulp.watch(path.LESS_ALL, [&apos;less&apos;]);
  gulp.watch(path.VENDOR, [&apos;vendor&apos;]);
  gulp.watch(path.JS, [&apos;lint&apos;, &apos;js&apos;]);
  gulp.watch(path.HTML, [&apos;html&apos;]);
  gulp.watch(path.IMG, [&apos;img&apos;]);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Default task.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;var all_tasks = [&apos;lint&apos;, &apos;css&apos;, &apos;less&apos;, &apos;vendor&apos;, &apos;js&apos;, &apos;html&apos;, &apos;img&apos;];

gulp.task(&apos;default&apos;, all_tasks);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;A More Complete gulpfile.js&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;/* gulp dependencies */
var gulp = require(&apos;gulp&apos;);
var less = require(&apos;gulp-less&apos;);
var watch = require(&apos;gulp-watch&apos;);
var imagemin = require(&apos;gulp-imagemin&apos;);
var connect = require(&apos;gulp-connect&apos;);
var concat = require(&apos;gulp-concat&apos;);
var sourcemaps = require(&apos;gulp-sourcemaps&apos;);
var uglify = require(&apos;gulp-uglify&apos;);
var ngAnnotate = require(&apos;gulp-ng-annotate&apos;);
var minifyCSS = require(&apos;gulp-minify-css&apos;);
var lessDependents = require(&apos;gulp-less-dependents&apos;);
var clean = require(&apos;gulp-clean&apos;);
var bower = require(&apos;gulp-bower&apos;);
var concat_vendor = require(&apos;gulp-concat-vendor&apos;);
var jshint = require(&apos;gulp-jshint&apos;);

/* path def */
var path = {
  HTML: [
  	&apos;src/.htaccess&apos;, 
  	&apos;src/*.html&apos;, 
  	&apos;src/views/**/*.html&apos;, 
  	&apos;src/views/*.html&apos;, 
  	&apos;src/favicon.png&apos;
  ],
  JS: [
  	&apos;src/js/*.js&apos;, 
  	&apos;src/js/**/*.js&apos;
  ],
  CSS: [
	  &apos;src/css/*.css&apos;
  ],
  LESS: [
  	&apos;src/less/style.less&apos;
  ],
  LESS_ALL: [
  	&apos;src/less/*.less&apos;
  ], 
  IMG: [
  	&apos;src/img/**&apos;
  ],
  VENDOR: [
  	&apos;bower_components/angular/angular.js&apos;, 
		&apos;bower_components/angular-animate/angular-animate.js&apos;, 
		&apos;bower_components/angular-aria/angular-aria.js&apos;, 
		&apos;bower_components/angular-messages/angular-messages.js&apos;,
		&apos;bower_components/angular-sanitize/angular-sanitize.js&apos;,  
		&apos;bower_components/angular-ui-router/release/angular-ui-router.js&apos;
    // ...and more
	],
  DIST: &apos;./dist&apos;
};

/* spin up distribution server */
gulp.task(&apos;connect&apos;, function() {
	connect.server({
		root: &apos;dist&apos;,
		port: 4005
	});
});

/* clean up dist dir */
gulp.task(&apos;clean&apos;, function() {
	return gulp.src(&apos;./dist/*&apos;, {force: true})
		.pipe(clean());
});

/* jslint for debugging */
gulp.task(&apos;lint&apos;, function() {
  return gulp.src(path.JS)
    .pipe(jshint())
    .pipe(jshint.reporter(&apos;default&apos;))
    .pipe(jshint.reporter(&apos;fail&apos;));
});

/* move css */
gulp.task(&apos;css&apos;, function () {
  gulp.src(path.CSS)
    .pipe(gulp.dest(path.DIST + &apos;/css&apos;));
});

/* compile less */
gulp.task(&apos;less&apos;, function () {
  gulp.src(path.LESS)
  	.pipe(lessDependents())
    .pipe(less())
    .pipe(minifyCSS())
    .pipe(gulp.dest(path.DIST + &apos;/css&apos;));
});

/* concat and compress app scripts */
gulp.task(&apos;js&apos;, function () {
  gulp.src(path.JS)
  	.pipe(sourcemaps.init())
		  .pipe(concat(&apos;app.js&apos;))
		  .pipe(ngAnnotate())
		  .pipe(uglify())
		.pipe(sourcemaps.write())
    .pipe(gulp.dest(path.DIST + &apos;/js&apos;));
});

/* concat vendor dependencies */
gulp.task(&apos;vendor&apos;, function () {
	gulp.src(path.VENDOR)
		.pipe(concat(&apos;vendor.js&apos;))
		.pipe(ngAnnotate())
		.pipe(uglify())
    .pipe(gulp.dest(path.DIST + &apos;/js&apos;));
});

/* copy over markups */
gulp.task(&apos;html&apos;, function(){
  gulp.src(path.HTML, {base: &apos;src&apos;})
    .pipe(gulp.dest(path.DIST));
});

/* compress images */
gulp.task(&apos;img&apos;, function(){
  gulp.src(path.IMG)
    .pipe(imagemin())
    .pipe(gulp.dest(path.DIST + &apos;/img&apos;));
});

/* watch all changes */
gulp.task(&apos;watch&apos;, function () {
  gulp.watch(path.LESS_ALL, [&apos;less&apos;]);
  gulp.watch(path.VENDOR, [&apos;vendor&apos;]);
  gulp.watch(path.JS, [&apos;lint&apos;, &apos;js&apos;]);
  gulp.watch(path.HTML, [&apos;html&apos;]);
  gulp.watch(path.IMG, [&apos;img&apos;]);
});

/* defualt */
gulp.task(&apos;default&apos;, all_tasks);

&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Create Virtual Hosts on&nbsp;MAMP]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p><a href="https://www.mamp.info/en/">MAMP</a> is a great GUI tool for creating server on localhost. But the free virsion of it only has very basic configurations. For example, creating a virtual host is not included. So I have to write the configurations on my own.</p>
<p>This article is based on Windows OS. Setup on Mac OS should be similar.</p>
<h1>Why Virtual Hosts</h1>
<p>Question: Why do we need to virtual hosts anyway? Answer: it simplifies your testing and developing. When developing a website, I want to visit it via something like <code>http://test.local</code>, rather than <code>http://localhost/test/</code>. The benefit of the former option is not just that it looks more like a real URL, or it is shorter. By creating <code>test.local</code> as the local domain, the root directory for your site is configured to any location your would like it to be, so there is no more hazard of nested path.</p>
<h1>Modify Hosts</h1>
<p>First, add the desired domain to the system&#8217;s host file. It is located at <code>C:\Windows\System32\drivers\etc\hosts</code>.</p>
<p>Add this line:</p>
<pre><code>127.0.0.1		test.local
</code></pre>
<p>You can replace <code>test.local</code> with almost anything of your choice.</p>
<h1>Modify Apache Configuration</h1>
<p>Modify <code>C:\MAMP\conf\apache\httpd.conf</code>.</p>
<p>Find this line:</p>
<pre><code># Virtual Hosts
# Include /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf
</code></pre>
<p>Change it to</p>
<pre><code># Virtual Hosts
Include C:\MAMP\conf\apache\httpd-vhosts.conf
</code></pre>
<h1>Create Virtual Host Configuration</h1>
<p>Now create the configuration file <code>httpd-vhosts.conf</code>.</p>
<pre><code>NameVirtualHost *:80

&lt;VirtualHost *:80&gt;
    DocumentRoot 'C:\MAMP\htdocs'
    ServerName localhost
&lt;/VirtualHost&gt;
 
&lt;VirtualHost *:80&gt;
    DocumentRoot 'C:\MAMP\htdocs\test'
    ServerName test.local
&lt;/VirtualHost&gt;
</code></pre>
<p>Start MAMP, your site should be available at <code>http://test.local</code>.</p>
</div>
]]></description><link>https://artifact.me/create-virtual-hosts-on-mamp</link><guid isPermaLink="false">https://artifact.me/create-virtual-hosts-on-mamp</guid><pubDate>Sun, 17 Jan 2016 06:08:54 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;&lt;a href=&quot;https://www.mamp.info/en/&quot;&gt;MAMP&lt;/a&gt; is a great GUI tool for creating server on localhost. But the free virsion of it only has very basic configurations. For example, creating a virtual host is not included. So I have to write the configurations on my own.&lt;/p&gt;
&lt;p&gt;This article is based on Windows OS. Setup on Mac OS should be similar.&lt;/p&gt;
&lt;h1&gt;Why Virtual Hosts&lt;/h1&gt;
&lt;p&gt;Question: Why do we need to virtual hosts anyway? Answer: it simplifies your testing and developing. When developing a website, I want to visit it via something like &lt;code&gt;http://test.local&lt;/code&gt;, rather than &lt;code&gt;http://localhost/test/&lt;/code&gt;. The benefit of the former option is not just that it looks more like a real URL, or it is shorter. By creating &lt;code&gt;test.local&lt;/code&gt; as the local domain, the root directory for your site is configured to any location your would like it to be, so there is no more hazard of nested path.&lt;/p&gt;
&lt;h1&gt;Modify Hosts&lt;/h1&gt;
&lt;p&gt;First, add the desired domain to the system&amp;#8217;s host file. It is located at &lt;code&gt;C:\Windows\System32\drivers\etc\hosts&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Add this line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;127.0.0.1		test.local
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can replace &lt;code&gt;test.local&lt;/code&gt; with almost anything of your choice.&lt;/p&gt;
&lt;h1&gt;Modify Apache Configuration&lt;/h1&gt;
&lt;p&gt;Modify &lt;code&gt;C:\MAMP\conf\apache\httpd.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Find this line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Virtual Hosts
# Include /Applications/MAMP/conf/apache/extra/httpd-vhosts.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change it to&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Virtual Hosts
Include C:\MAMP\conf\apache\httpd-vhosts.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Create Virtual Host Configuration&lt;/h1&gt;
&lt;p&gt;Now create the configuration file &lt;code&gt;httpd-vhosts.conf&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;NameVirtualHost *:80

&amp;lt;VirtualHost *:80&amp;gt;
    DocumentRoot &apos;C:\MAMP\htdocs&apos;
    ServerName localhost
&amp;lt;/VirtualHost&amp;gt;
 
&amp;lt;VirtualHost *:80&amp;gt;
    DocumentRoot &apos;C:\MAMP\htdocs\test&apos;
    ServerName test.local
&amp;lt;/VirtualHost&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Start MAMP, your site should be available at &lt;code&gt;http://test.local&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
</content:encoded></item><item><title><![CDATA[Serve Ghost and Nginx on Ubuntu 14.04&nbsp;LTS]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>Recently I switched from <a href="https://www.linode.com/">Linode</a> to <a href="https://www.digitalocean.com/">DigitalOcean</a> because of some rumor that Linode website is blocked in China. Here is a memo of how I migrated a Ghost blog, <a href="https://otakism.org">otakism.org</a>.</p>
<p>Some content came from several tutorials from DigitalOcean community. Credits are given at the end of this article.</p>
<h1>Install Nginx</h1>
<p>Install Nginx via <code>apt-get</code>.</p>
<pre><code class="language-bash">sudo apt-get update
sudo apt-get install nginx
</code></pre>
<h1>Create Site Directories</h1>
<p>I prefer <code>/var/www</code> as the root directory. To do that I created the following folders to hold my website files.</p>
<pre><code class="language-bash">sudo mkdir -p /var/www/rakugaki.me/html
sudo mkdir -p /var/www/otakism.org/html
sudo chown -R $USER:$USER /var/www/rakugaki.me/html
sudo chown -R $USER:$USER /var/www/otakism.org/html
sudo chmod -R 755 /var/www
</code></pre>
<h1>Create Server Block Files</h1>
<p>Server blocks configure ports and root directories for each website, as well as details about how they should be served.</p>
<p>Here I used <a href="https://rakugaki.me">rakugaki.me</a> as the default domain of my server.</p>
<pre><code class="language-bash">sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/rakugaki.me
sudo vim /etc/nginx/sites-available/rakugaki.me
</code></pre>
<p>Change the configuration like this:</p>
<pre><code>server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/rakugaki.me/html;
    index index.html index.htm;

    server_name rakugaki.me, www.rakugaki.me;

    location / {
        try_files $uri $uri/ =404;
    }
}
</code></pre>
<p>Then I created the second server block file for <a href="http://otakism.org">otakism.org</a>. Since it is a Ghost blog, the file should be like this:</p>
<pre><code>server {
    listen 80;
    listen [::]:80;
    server_name otakism.org;
    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:2368;
    }
}
</code></pre>
<p>The files are done but we are not done. Enable the server block files.</p>
<pre><code class="language-bash">sudo ln -s /etc/nginx/sites-available/rakugaki.me /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/otakism.org /etc/nginx/sites-enabled/
</code></pre>
<p>Delete the default site generated by Nginx.</p>
<pre><code class="language-bash">sudo rm /etc/nginx/sites-enabled/default
</code></pre>
<p>Restart Nginx to apply new settings.</p>
<pre><code class="language-bash">sudo service nginx restart
</code></pre>
<h1>Install Ghost</h1>
<p>Prerequisites first. Ghost requires <a href="https://nodejs.org/en/">Node.js</a> and <a href="https://www.npmjs.com/">npm</a>. These can be installed in various ways. Refer to <a href="https://www.digitalocean.com/community/tutorials/how-to-create-a-blog-with-ghost-and-nginx-on-ubuntu-14-04">this article</a> for more options.</p>
<pre><code class="language-bash">sudo apt-get update
sudo apt-get install nodejs
sudo apt-get install npm
</code></pre>
<p>Download Ghost. Details about the remaining steps in this chapter can be found in the <a href="http://support.ghost.org/installing-ghost-linux/">official guide</a> at Ghost.org.</p>
<pre><code class="language-bash">sudo apt-get update
sudo apt-get install zip wget

cd /var/www/otakism.org/html
sudo wget https://ghost.org/zip/ghost-latest.zip
sudo unzip -d ghost ghost-latest.zip
</code></pre>
<p>Install it.</p>
<pre><code class="language-bash">cd ghost/
sudo npm install --production
</code></pre>
<h1>Configure Ghost</h1>
<p>Change the url and/or port under production section.</p>
<pre><code class="language-bash">sudo cp config.example.js config.js
sudo vim config.js
</code></pre>
<p>I also needed to migrate data, images and themes. Copy or replace them under <code>content/</code>.</p>
<h1>Keep Ghost Running</h1>
<p>Create upstart configurations to make the ghost blog start on system boot. In this way I can also manage my ghost blog as a service.</p>
<pre><code class="language-bash">sudo /etc/init/vim ghost-otakism.conf
</code></pre>
<p>Paste the following lines.</p>
<pre><code># ghost-otakism

start on startup

script
    cd /var/www/otakism.org/html/ghost
    npm start --production
end script
</code></pre>
<p>Finally I can summon my ghost.</p>
<pre><code class="language-bash">sudo service ghost-otakism start
</code></pre>
<p>Contents about Nginx setup credits to Justin Ellingwood, original post at <a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-14-04-lts">How To Set Up Nginx Server Blocks (Virtual Hosts) on Ubuntu 14.04 LTS</a>.</p>
</div>



<p class="wp-block-paragraph"></p>
]]></description><link>https://artifact.me/serve-ghost-and-nginx-on-ubuntu-14-04-lts</link><guid isPermaLink="false">https://artifact.me/serve-ghost-and-nginx-on-ubuntu-14-04-lts</guid><pubDate>Sun, 10 Jan 2016 06:06:35 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;Recently I switched from &lt;a href=&quot;https://www.linode.com/&quot;&gt;Linode&lt;/a&gt; to &lt;a href=&quot;https://www.digitalocean.com/&quot;&gt;DigitalOcean&lt;/a&gt; because of some rumor that Linode website is blocked in China. Here is a memo of how I migrated a Ghost blog, &lt;a href=&quot;https://otakism.org&quot;&gt;otakism.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Some content came from several tutorials from DigitalOcean community. Credits are given at the end of this article.&lt;/p&gt;
&lt;h1&gt;Install Nginx&lt;/h1&gt;
&lt;p&gt;Install Nginx via &lt;code&gt;apt-get&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get update
sudo apt-get install nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Create Site Directories&lt;/h1&gt;
&lt;p&gt;I prefer &lt;code&gt;/var/www&lt;/code&gt; as the root directory. To do that I created the following folders to hold my website files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo mkdir -p /var/www/rakugaki.me/html
sudo mkdir -p /var/www/otakism.org/html
sudo chown -R $USER:$USER /var/www/rakugaki.me/html
sudo chown -R $USER:$USER /var/www/otakism.org/html
sudo chmod -R 755 /var/www
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Create Server Block Files&lt;/h1&gt;
&lt;p&gt;Server blocks configure ports and root directories for each website, as well as details about how they should be served.&lt;/p&gt;
&lt;p&gt;Here I used &lt;a href=&quot;https://rakugaki.me&quot;&gt;rakugaki.me&lt;/a&gt; as the default domain of my server.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/rakugaki.me
sudo vim /etc/nginx/sites-available/rakugaki.me
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Change the configuration like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    root /var/www/rakugaki.me/html;
    index index.html index.htm;

    server_name rakugaki.me, www.rakugaki.me;

    location / {
        try_files $uri $uri/ =404;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then I created the second server block file for &lt;a href=&quot;http://otakism.org&quot;&gt;otakism.org&lt;/a&gt;. Since it is a Ghost blog, the file should be like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80;
    listen [::]:80;
    server_name otakism.org;
    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:2368;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The files are done but we are not done. Enable the server block files.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo ln -s /etc/nginx/sites-available/rakugaki.me /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/otakism.org /etc/nginx/sites-enabled/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Delete the default site generated by Nginx.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo rm /etc/nginx/sites-enabled/default
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Restart Nginx to apply new settings.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo service nginx restart
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Install Ghost&lt;/h1&gt;
&lt;p&gt;Prerequisites first. Ghost requires &lt;a href=&quot;https://nodejs.org/en/&quot;&gt;Node.js&lt;/a&gt; and &lt;a href=&quot;https://www.npmjs.com/&quot;&gt;npm&lt;/a&gt;. These can be installed in various ways. Refer to &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-create-a-blog-with-ghost-and-nginx-on-ubuntu-14-04&quot;&gt;this article&lt;/a&gt; for more options.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get update
sudo apt-get install nodejs
sudo apt-get install npm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Download Ghost. Details about the remaining steps in this chapter can be found in the &lt;a href=&quot;http://support.ghost.org/installing-ghost-linux/&quot;&gt;official guide&lt;/a&gt; at Ghost.org.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo apt-get update
sudo apt-get install zip wget

cd /var/www/otakism.org/html
sudo wget https://ghost.org/zip/ghost-latest.zip
sudo unzip -d ghost ghost-latest.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install it.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ghost/
sudo npm install --production
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Configure Ghost&lt;/h1&gt;
&lt;p&gt;Change the url and/or port under production section.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo cp config.example.js config.js
sudo vim config.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also needed to migrate data, images and themes. Copy or replace them under &lt;code&gt;content/&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Keep Ghost Running&lt;/h1&gt;
&lt;p&gt;Create upstart configurations to make the ghost blog start on system boot. In this way I can also manage my ghost blog as a service.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo /etc/init/vim ghost-otakism.conf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Paste the following lines.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ghost-otakism

start on startup

script
    cd /var/www/otakism.org/html/ghost
    npm start --production
end script
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally I can summon my ghost.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;sudo service ghost-otakism start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Contents about Nginx setup credits to Justin Ellingwood, original post at &lt;a href=&quot;https://www.digitalocean.com/community/tutorials/how-to-set-up-nginx-server-blocks-virtual-hosts-on-ubuntu-14-04-lts&quot;&gt;How To Set Up Nginx Server Blocks (Virtual Hosts) on Ubuntu 14.04 LTS&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;&lt;/p&gt;
</content:encoded></item><item><title><![CDATA[Ruby on Rails Configuration on Mac OS X&nbsp;10.11]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p>This is a memo of how to setup <a href="http://rubyonrails.org/">Ruby-on-Rails</a> on Mac OS X 10.11. Especially, to get it work on <a href="https://www.jetbrains.com/ruby/">RubyMine 8.0.3</a>.</p>
<p>The following process might not be the solution for everyone.</p>
<h1>Command-Line Tools</h1>
<p>First of all, make sure developer command-line tools is installed.</p>
<pre><code class="language-bash">xcode-select --install
</code></pre>
<p>If it is already installed, expected output should be:</p>
<pre><code class="language-bash">xcode-select: error: command line tools are already installed, use &quot;Software Update&quot; to install updates
</code></pre>
<h1>Homebrew</h1>
<p>Install <a href="http://brew.sh/">Homebrew</a>:</p>
<pre><code class="language-bash">ruby -e &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&quot;
</code></pre>
<p>Mac OS X 10.11 comes with version 2.0.0 of Ruby so this command should work.</p>
<p>Then we can install almost all the packages we need via homebrew.</p>
<h1>Ruby</h1>
<p>Use the officially recommended way to manage ruby versions with rbenv.</p>
<pre><code class="language-bash">brew install rbenv ruby-build
rbenv install 2.2.4
rbenv global 2.2.4
</code></pre>
<p>Check version:</p>
<pre><code class="language-bash">ruby -v
</code></pre>
<h1>Dependencies</h1>
<p>If I remember correctly.</p>
<pre><code class="language-bash">brew update
brew upgrade
brew install libxml2 libxslt libiconv
gem install nokogiri -- --use-system-libraries --with-xml2-include=/usr/include/libxml2 --with-xml2-lib=/usr/lib/
brew install mysql
gem install mysql
</code></pre>
</div>



<p class="wp-block-paragraph"></p>
]]></description><link>https://artifact.me/ruby-on-rails-config-mac</link><guid isPermaLink="false">https://artifact.me/ruby-on-rails-config-mac</guid><pubDate>Fri, 25 Dec 2015 06:02:47 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;This is a memo of how to setup &lt;a href=&quot;http://rubyonrails.org/&quot;&gt;Ruby-on-Rails&lt;/a&gt; on Mac OS X 10.11. Especially, to get it work on &lt;a href=&quot;https://www.jetbrains.com/ruby/&quot;&gt;RubyMine 8.0.3&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The following process might not be the solution for everyone.&lt;/p&gt;
&lt;h1&gt;Command-Line Tools&lt;/h1&gt;
&lt;p&gt;First of all, make sure developer command-line tools is installed.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xcode-select --install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it is already installed, expected output should be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;xcode-select: error: command line tools are already installed, use &amp;quot;Software Update&amp;quot; to install updates
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Homebrew&lt;/h1&gt;
&lt;p&gt;Install &lt;a href=&quot;http://brew.sh/&quot;&gt;Homebrew&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ruby -e &amp;quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mac OS X 10.11 comes with version 2.0.0 of Ruby so this command should work.&lt;/p&gt;
&lt;p&gt;Then we can install almost all the packages we need via homebrew.&lt;/p&gt;
&lt;h1&gt;Ruby&lt;/h1&gt;
&lt;p&gt;Use the officially recommended way to manage ruby versions with rbenv.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew install rbenv ruby-build
rbenv install 2.2.4
rbenv global 2.2.4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check version:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;ruby -v
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Dependencies&lt;/h1&gt;
&lt;p&gt;If I remember correctly.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;brew update
brew upgrade
brew install libxml2 libxslt libiconv
gem install nokogiri -- --use-system-libraries --with-xml2-include=/usr/include/libxml2 --with-xml2-lib=/usr/lib/
brew install mysql
gem install mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;



&lt;p class=&quot;wp-block-paragraph&quot;&gt;&lt;/p&gt;
</content:encoded></item><item><title><![CDATA[Hexo Theme Fact]]></title><description><![CDATA[
<div class="wp-block-jetpack-markdown"><p><strong>Fact</strong> is a simple but powerful theme for <a href="https://hexo.io/">Hexo</a>. It comes with integration of tags, categories, archive and table-of-contents. The style design is inspired by <a href="https://www.v2ex.com/">V2EX</a>.</p>
<p>The theme is available on my Github as a public repository <a href="https://github.com/artchen/hexo-theme-fact">hexo-theme-fact</a>.</p>
<h1>Installation</h1>
<p>I assume you already have a Hexo blog initialized.</p>
<pre><code class="language-bash">cd /themes
git clone https://github.com/artchen/hexo-theme-fact.git fact
</code></pre>
<p>Don’t forget to modify <code>_config.yml</code> under root directory of Hexo blog. Change theme to Fact.</p>
<pre><code class="language-javascript">theme: fact
</code></pre>
<h1>Configuration</h1>
<p>Some configuration can be found in <code>_config.yml</code> under the <code>themes/fact</code> directory.</p>
<h1>Update</h1>
<pre><code class="language-bash">cd /themes/fact
git pull
</code></pre>
<h1>Fonts</h1>
<p>I used <a href="https://typekit.com/fonts/futura-pt">Futura</a> for text, and <a href="https://typekit.com/fonts/inconsolata">Inconsolata</a> for source code. Both of them are hosted on <a href="https://typekit.com/fonts">Typekit</a>.</p>
<p>If you do not use Typekit, here are the necessary steps to switch to other services:</p>
<ol>
<li>In <code>layout/_partial/head.ejs</code>, delete the lines that include the typekit library, then include necessary files from your choice of web fonts (eg: <a href="https://www.google.com/fonts">Google Font</a>).</li>
<li>In <code>source/css/_sass/style.scss</code>, change the font macros to your choices.</li>
</ol>
<h1>Screenshot</h1>
<p><a href="https://cdn-artifact.otakism.com/images/themes/hexo-theme-fact-screenshot.png"><img src="https://cdn-artifact.otakism.com/images/themes/hexo-theme-fact-screenshot.png" alt="Fact Screenshot"/></a></p>
</div>
		<div id="geo-post-3" class="geo geo-post" style="display: none">
			<span class="latitude">43.073052</span>
			<span class="longitude">-89.401230</span>
		</div>]]></description><link>https://artifact.me/hexo-theme-fact</link><guid isPermaLink="false">https://artifact.me/hexo-theme-fact</guid><pubDate>Fri, 13 Nov 2015 10:51:52 GMT</pubDate><content:encoded>
&lt;div class=&quot;wp-block-jetpack-markdown&quot;&gt;&lt;p&gt;&lt;strong&gt;Fact&lt;/strong&gt; is a simple but powerful theme for &lt;a href=&quot;https://hexo.io/&quot;&gt;Hexo&lt;/a&gt;. It comes with integration of tags, categories, archive and table-of-contents. The style design is inspired by &lt;a href=&quot;https://www.v2ex.com/&quot;&gt;V2EX&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The theme is available on my Github as a public repository &lt;a href=&quot;https://github.com/artchen/hexo-theme-fact&quot;&gt;hexo-theme-fact&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Installation&lt;/h1&gt;
&lt;p&gt;I assume you already have a Hexo blog initialized.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /themes
git clone https://github.com/artchen/hexo-theme-fact.git fact
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don’t forget to modify &lt;code&gt;_config.yml&lt;/code&gt; under root directory of Hexo blog. Change theme to Fact.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;theme: fact
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Configuration&lt;/h1&gt;
&lt;p&gt;Some configuration can be found in &lt;code&gt;_config.yml&lt;/code&gt; under the &lt;code&gt;themes/fact&lt;/code&gt; directory.&lt;/p&gt;
&lt;h1&gt;Update&lt;/h1&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd /themes/fact
git pull
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Fonts&lt;/h1&gt;
&lt;p&gt;I used &lt;a href=&quot;https://typekit.com/fonts/futura-pt&quot;&gt;Futura&lt;/a&gt; for text, and &lt;a href=&quot;https://typekit.com/fonts/inconsolata&quot;&gt;Inconsolata&lt;/a&gt; for source code. Both of them are hosted on &lt;a href=&quot;https://typekit.com/fonts&quot;&gt;Typekit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you do not use Typekit, here are the necessary steps to switch to other services:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In &lt;code&gt;layout/_partial/head.ejs&lt;/code&gt;, delete the lines that include the typekit library, then include necessary files from your choice of web fonts (eg: &lt;a href=&quot;https://www.google.com/fonts&quot;&gt;Google Font&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;In &lt;code&gt;source/css/_sass/style.scss&lt;/code&gt;, change the font macros to your choices.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Screenshot&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://cdn-artifact.otakism.com/images/themes/hexo-theme-fact-screenshot.png&quot;&gt;&lt;img src=&quot;https://cdn-artifact.otakism.com/images/themes/hexo-theme-fact-screenshot.png&quot; alt=&quot;Fact Screenshot&quot;/&gt;&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
		&lt;div id=&quot;geo-post-3&quot; class=&quot;geo geo-post&quot; style=&quot;display: none&quot;&gt;
			&lt;span class=&quot;latitude&quot;&gt;43.073052&lt;/span&gt;
			&lt;span class=&quot;longitude&quot;&gt;-89.401230&lt;/span&gt;
		&lt;/div&gt;</content:encoded></item></channel></rss>