summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVictor Toso <victortoso@redhat.com>2015-10-30 11:49:46 +0100
committerVictor Toso <victortoso@redhat.com>2015-10-30 11:49:46 +0100
commite4d5e93c0174577988c25d2de89158def5054e4f (patch)
tree5a3595af33eb3e85abdeb11da22282db780e740f
parent9b36a32b5208455bc91fcce64186869be62888bb (diff)
search: enable tipue_search as default search engine
tipue_search plugin generate the tipuesearch_content.json which is used from tipuesearch engine to do search in static pages. It is also necessary the sitemap plugin which generates the sitemap.xml in order to have the urls for each result. A basic search.html template is also included to provide the results.
-rw-r--r--pelicanconf.py4
-rw-r--r--plugins/sitemap/Readme.rst74
-rw-r--r--plugins/sitemap/__init__.py1
-rw-r--r--plugins/sitemap/sitemap.py254
-rw-r--r--plugins/tipue_search/README.md67
-rw-r--r--plugins/tipue_search/__init__.py1
-rw-r--r--plugins/tipue_search/tipue_search.py116
-rw-r--r--spice-space/static/tipuesearch/img/loader.gifbin0 -> 4178 bytes
-rw-r--r--spice-space/static/tipuesearch/img/search.pngbin0 -> 315 bytes
-rw-r--r--spice-space/static/tipuesearch/tipuesearch.css134
-rw-r--r--spice-space/static/tipuesearch/tipuesearch.js372
-rw-r--r--spice-space/static/tipuesearch/tipuesearch.min.js12
-rw-r--r--spice-space/static/tipuesearch/tipuesearch_set.js52
-rw-r--r--spice-space/templates/base.html23
-rw-r--r--spice-space/templates/search.html26
15 files changed, 1130 insertions, 6 deletions
diff --git a/pelicanconf.py b/pelicanconf.py
index 518e086..dc88ab9 100644
--- a/pelicanconf.py
+++ b/pelicanconf.py
@@ -14,7 +14,8 @@ TIMEZONE = 'Europe/Paris'
DEFAULT_LANG = u'en'
PLUGIN_PATHS = ["plugins"]
-PLUGINS = ["asciidoc_reader"]
+PLUGINS = ["asciidoc_reader", "tipue_search", "sitemap"]
+SITEMAP = {'format' : 'xml'}
ASCIIDOC_OPTIONS = ["-f asciidoc.conf", "-a iconsdir="+SITEURL + "/theme/images/icons"]
ASCIIDOC_BACKEND = "html5"
@@ -22,6 +23,7 @@ ASCIIDOC_BACKEND = "html5"
# Static content that we reference in our site goes into folders appointed
# by this path
STATIC_PATHS = ['static/docs', 'static/images']
+DIRECT_TEMPLATES = ['search']
# Feed generation is usually not desired when developing
FEED_ALL_ATOM = None
diff --git a/plugins/sitemap/Readme.rst b/plugins/sitemap/Readme.rst
new file mode 100644
index 0000000..719c38b
--- /dev/null
+++ b/plugins/sitemap/Readme.rst
@@ -0,0 +1,74 @@
+Sitemap
+-------
+
+This plugin generates plain-text or XML sitemaps. You can use the ``SITEMAP``
+variable in your settings file to configure the behavior of the plugin.
+
+The ``SITEMAP`` variable must be a Python dictionary and can contain these keys:
+
+- ``format``, which sets the output format of the plugin (``xml`` or ``txt``)
+
+- ``priorities``, which is a dictionary with three keys:
+
+ - ``articles``, the priority for the URLs of the articles and their
+ translations
+
+ - ``pages``, the priority for the URLs of the static pages
+
+ - ``indexes``, the priority for the URLs of the index pages, such as tags,
+ author pages, categories indexes, archives, etc...
+
+ All the values of this dictionary must be decimal numbers between ``0`` and ``1``.
+
+- ``changefreqs``, which is a dictionary with three items:
+
+ - ``articles``, the update frequency of the articles
+
+ - ``pages``, the update frequency of the pages
+
+ - ``indexes``, the update frequency of the index pages
+
+ Valid frequency values are ``always``, ``hourly``, ``daily``, ``weekly``, ``monthly``,
+ ``yearly`` and ``never``.
+
+You can exclude URLs from being included in the sitemap via regular expressions.
+For example, to exclude all URLs containing ``tag/`` or ``category/`` you can
+use the following ``SITEMAP`` setting.
+
+.. code-block:: python
+
+ SITEMAP = {
+ 'exclude': ['tag/', 'category/']
+ }
+
+If a key is missing or a value is incorrect, it will be replaced with the
+default value.
+
+The sitemap is saved in ``<output_path>/sitemap.<format>``.
+
+.. note::
+ ``priorities`` and ``changefreqs`` are information for search engines.
+ They are only used in the XML sitemaps.
+ For more information: <http://www.sitemaps.org/protocol.html#xmlTagDefinitions>
+
+**Example**
+
+Here is an example configuration (it's also the default settings):
+
+.. code-block:: python
+
+ PLUGINS=['pelican.plugins.sitemap',]
+
+ SITEMAP = {
+ 'format': 'xml',
+ 'priorities': {
+ 'articles': 0.5,
+ 'indexes': 0.5,
+ 'pages': 0.5
+ },
+ 'changefreqs': {
+ 'articles': 'monthly',
+ 'indexes': 'daily',
+ 'pages': 'monthly'
+ }
+ }
diff --git a/plugins/sitemap/__init__.py b/plugins/sitemap/__init__.py
new file mode 100644
index 0000000..6523d3a
--- /dev/null
+++ b/plugins/sitemap/__init__.py
@@ -0,0 +1 @@
+from .sitemap import * \ No newline at end of file
diff --git a/plugins/sitemap/sitemap.py b/plugins/sitemap/sitemap.py
new file mode 100644
index 0000000..ccd9bfc
--- /dev/null
+++ b/plugins/sitemap/sitemap.py
@@ -0,0 +1,254 @@
+# -*- coding: utf-8 -*-
+'''
+Sitemap
+-------
+
+The sitemap plugin generates plain-text or XML sitemaps.
+'''
+
+from __future__ import unicode_literals
+
+import re
+import collections
+import os.path
+
+from datetime import datetime
+from logging import warning, info
+from codecs import open
+from pytz import timezone
+
+from pelican import signals, contents
+from pelican.utils import get_date
+
+TXT_HEADER = """{0}/index.html
+{0}/archives.html
+{0}/tags.html
+{0}/categories.html
+"""
+
+XML_HEADER = """<?xml version="1.0" encoding="utf-8"?>
+<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"
+xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+"""
+
+XML_URL = """
+<url>
+<loc>{0}/{1}</loc>
+<lastmod>{2}</lastmod>
+<changefreq>{3}</changefreq>
+<priority>{4}</priority>
+</url>
+"""
+
+XML_FOOTER = """
+</urlset>
+"""
+
+
+def format_date(date):
+ if date.tzinfo:
+ tz = date.strftime('%z')
+ tz = tz[:-2] + ':' + tz[-2:]
+ else:
+ tz = "-00:00"
+ return date.strftime("%Y-%m-%dT%H:%M:%S") + tz
+
+class SitemapGenerator(object):
+
+ def __init__(self, context, settings, path, theme, output_path, *null):
+
+ self.output_path = output_path
+ self.context = context
+ self.now = datetime.now()
+ self.siteurl = settings.get('SITEURL')
+
+
+ self.default_timezone = settings.get('TIMEZONE', 'UTC')
+ self.timezone = getattr(self, 'timezone', self.default_timezone)
+ self.timezone = timezone(self.timezone)
+
+ self.format = 'xml'
+
+ self.changefreqs = {
+ 'articles': 'monthly',
+ 'indexes': 'daily',
+ 'pages': 'monthly'
+ }
+
+ self.priorities = {
+ 'articles': 0.5,
+ 'indexes': 0.5,
+ 'pages': 0.5
+ }
+
+ self.sitemapExclude = []
+
+ config = settings.get('SITEMAP', {})
+
+ if not isinstance(config, dict):
+ warning("sitemap plugin: the SITEMAP setting must be a dict")
+ else:
+ fmt = config.get('format')
+ pris = config.get('priorities')
+ chfreqs = config.get('changefreqs')
+ self.sitemapExclude = config.get('exclude', [])
+
+ if fmt not in ('xml', 'txt'):
+ warning("sitemap plugin: SITEMAP['format'] must be `txt' or `xml'")
+ warning("sitemap plugin: Setting SITEMAP['format'] on `xml'")
+ elif fmt == 'txt':
+ self.format = fmt
+ return
+
+ valid_keys = ('articles', 'indexes', 'pages')
+ valid_chfreqs = ('always', 'hourly', 'daily', 'weekly', 'monthly',
+ 'yearly', 'never')
+
+ if isinstance(pris, dict):
+ # We use items for Py3k compat. .iteritems() otherwise
+ for k, v in pris.items():
+ if k in valid_keys and not isinstance(v, (int, float)):
+ default = self.priorities[k]
+ warning("sitemap plugin: priorities must be numbers")
+ warning("sitemap plugin: setting SITEMAP['priorities']"
+ "['{0}'] on {1}".format(k, default))
+ pris[k] = default
+ self.priorities.update(pris)
+ elif pris is not None:
+ warning("sitemap plugin: SITEMAP['priorities'] must be a dict")
+ warning("sitemap plugin: using the default values")
+
+ if isinstance(chfreqs, dict):
+ # .items() for py3k compat.
+ for k, v in chfreqs.items():
+ if k in valid_keys and v not in valid_chfreqs:
+ default = self.changefreqs[k]
+ warning("sitemap plugin: invalid changefreq `{0}'".format(v))
+ warning("sitemap plugin: setting SITEMAP['changefreqs']"
+ "['{0}'] on '{1}'".format(k, default))
+ chfreqs[k] = default
+ self.changefreqs.update(chfreqs)
+ elif chfreqs is not None:
+ warning("sitemap plugin: SITEMAP['changefreqs'] must be a dict")
+ warning("sitemap plugin: using the default values")
+
+ def write_url(self, page, fd):
+
+ if getattr(page, 'status', 'published') != 'published':
+ return
+
+ # We can disable categories/authors/etc by using False instead of ''
+ if not page.save_as:
+ return
+
+ page_path = os.path.join(self.output_path, page.save_as)
+ if not os.path.exists(page_path):
+ return
+
+ lastdate = getattr(page, 'date', self.now)
+ try:
+ lastdate = self.get_date_modified(page, lastdate)
+ except ValueError:
+ warning("sitemap plugin: " + page.save_as + " has invalid modification date,")
+ warning("sitemap plugin: using date value as lastmod.")
+ lastmod = format_date(lastdate)
+
+ if isinstance(page, contents.Article):
+ pri = self.priorities['articles']
+ chfreq = self.changefreqs['articles']
+ elif isinstance(page, contents.Page):
+ pri = self.priorities['pages']
+ chfreq = self.changefreqs['pages']
+ else:
+ pri = self.priorities['indexes']
+ chfreq = self.changefreqs['indexes']
+
+ pageurl = '' if page.url == 'index.html' else page.url
+
+ #Exclude URLs from the sitemap:
+ if self.format == 'xml':
+ flag = False
+ for regstr in self.sitemapExclude:
+ if re.match(regstr, pageurl):
+ flag = True
+ break
+ if not flag:
+ fd.write(XML_URL.format(self.siteurl, pageurl, lastmod, chfreq, pri))
+ else:
+ fd.write(self.siteurl + '/' + pageurl + '\n')
+
+ def get_date_modified(self, page, default):
+ if hasattr(page, 'modified'):
+ if isinstance(page.modified, datetime):
+ return page.modified
+ return get_date(page.modified)
+ else:
+ return default
+
+ def set_url_wrappers_modification_date(self, wrappers):
+ for (wrapper, articles) in wrappers:
+ lastmod = datetime.min.replace(tzinfo=self.timezone)
+ for article in articles:
+ lastmod = max(lastmod, article.date.replace(tzinfo=self.timezone))
+ try:
+ modified = self.get_date_modified(article, datetime.min).replace(tzinfo=self.timezone)
+ lastmod = max(lastmod, modified)
+ except ValueError:
+ # Supressed: user will be notified.
+ pass
+ setattr(wrapper, 'modified', str(lastmod))
+
+ def generate_output(self, writer):
+ path = os.path.join(self.output_path, 'sitemap.{0}'.format(self.format))
+
+ pages = self.context['pages'] + self.context['articles'] \
+ + [ c for (c, a) in self.context['categories']] \
+ + [ t for (t, a) in self.context['tags']] \
+ + [ a for (a, b) in self.context['authors']]
+
+ self.set_url_wrappers_modification_date(self.context['categories'])
+ self.set_url_wrappers_modification_date(self.context['tags'])
+ self.set_url_wrappers_modification_date(self.context['authors'])
+
+ for article in self.context['articles']:
+ pages += article.translations
+
+ info('writing {0}'.format(path))
+
+ with open(path, 'w', encoding='utf-8') as fd:
+
+ if self.format == 'xml':
+ fd.write(XML_HEADER)
+ else:
+ fd.write(TXT_HEADER.format(self.siteurl))
+
+ FakePage = collections.namedtuple('FakePage',
+ ['status',
+ 'date',
+ 'url',
+ 'save_as'])
+
+ for standard_page_url in ['index.html',
+ 'archives.html',
+ 'tags.html',
+ 'categories.html']:
+ fake = FakePage(status='published',
+ date=self.now,
+ url=standard_page_url,
+ save_as=standard_page_url)
+ self.write_url(fake, fd)
+
+ for page in pages:
+ self.write_url(page, fd)
+
+ if self.format == 'xml':
+ fd.write(XML_FOOTER)
+
+
+def get_generators(generators):
+ return SitemapGenerator
+
+
+def register():
+ signals.get_generators.connect(get_generators)
diff --git a/plugins/tipue_search/README.md b/plugins/tipue_search/README.md
new file mode 100644
index 0000000..1a2d615
--- /dev/null
+++ b/plugins/tipue_search/README.md
@@ -0,0 +1,67 @@
+Tipue Search
+============
+
+A Pelican plugin to serialize generated HTML to JSON that can be used by jQuery plugin - Tipue Search.
+
+Copyright (c) Talha Mansoor
+
+Author | Talha Mansoor
+----------------|-----
+Author Email | talha131@gmail.com
+Author Homepage | http://onCrashReboot.com
+Github Account | https://github.com/talha131
+
+Why do you need it?
+===================
+
+Static sites do not offer search feature out of the box. [Tipue Search](http://www.tipue.com/search/)
+is a jQuery plugin that search the static site without using any third party service, like DuckDuckGo or Google.
+
+Tipue Search offers 4 search modes. Its [JSON search mode](http://www.tipue.com/search/docs/json/) is the best search mode
+especially for large sites.
+
+Tipue's JSON search mode requires the textual content of site in JSON format.
+
+Requirements
+============
+
+Tipue Search requires BeautifulSoup.
+
+```bash
+pip install beautifulsoup4
+```
+
+How Tipue Search works
+=========================
+
+Tipue Search serializes the generated HTML into JSON. Format of JSON is as follows
+
+```python
+{
+ "pages": [
+ {
+ "text": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. Duis sagittis ipsum. Praesent mauris. Fusce nec tellus sed augue semper porta. Mauris massa. Vestibulum lacinia arcu eget nulla. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur sodales ligula in libero.",
+ "tags": "Example Category",
+ "url" : "http://oncrashreboot.com/plugin-example.html",
+ "title": "Everything you want to know about Lorem Ipsum"
+ },
+ {
+ "text": "Sed dignissim lacinia nunc. Curabitur tortor. Pellentesque nibh. Aenean quam. In scelerisque sem at dolor. Maecenas mattis. Sed convallis tristique sem. Proin ut ligula vel nunc egestas porttitor. Morbi lectus risus, iaculis vel, suscipit quis, luctus non, massa. Fusce ac turpis quis ligula lacinia aliquet. Mauris ipsum. Nulla metus metus, ullamcorper vel, tincidunt sed, euismod in, nibh.",
+ "tags": "Example Category",
+ "url" : "http://oncrashreboot.com/plugin-example-2.html",
+ "title": "Review of the book Lorem Ipsum"
+ }
+ ]
+}
+```
+
+JSON is written to file `tipuesearch_content.json` which is created in the root of `output` directory.
+
+How to use
+==========
+
+To utilize JSON Search mode, your theme needs to have Tipue Search properly configured in it. [Official documentation](http://www.tipue.com/search/docs/#json) has the required details.
+
+Pelican [Elegant Theme](https://github.com/talha131/pelican-elegant) and [Plumage
+theme](https://github.com/kdeldycke/plumage) have Tipue Search configured. You can view their
+code to understand the configuration.
diff --git a/plugins/tipue_search/__init__.py b/plugins/tipue_search/__init__.py
new file mode 100644
index 0000000..ebd6c06
--- /dev/null
+++ b/plugins/tipue_search/__init__.py
@@ -0,0 +1 @@
+from .tipue_search import *
diff --git a/plugins/tipue_search/tipue_search.py b/plugins/tipue_search/tipue_search.py
new file mode 100644
index 0000000..b27ef77
--- /dev/null
+++ b/plugins/tipue_search/tipue_search.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+"""
+Tipue Search
+============
+
+A Pelican plugin to serialize generated HTML to JSON
+that can be used by jQuery plugin - Tipue Search.
+
+Copyright (c) Talha Mansoor
+"""
+
+from __future__ import unicode_literals
+
+import os.path
+import json
+from bs4 import BeautifulSoup
+from codecs import open
+try:
+ from urlparse import urljoin
+except ImportError:
+ from urllib.parse import urljoin
+
+from pelican import signals
+
+
+class Tipue_Search_JSON_Generator(object):
+
+ def __init__(self, context, settings, path, theme, output_path, *null):
+
+ self.output_path = output_path
+ self.context = context
+ self.siteurl = settings.get('SITEURL')
+ self.tpages = settings.get('TEMPLATE_PAGES')
+ self.output_path = output_path
+ self.json_nodes = []
+
+
+ def create_json_node(self, page):
+
+ if getattr(page, 'status', 'published') != 'published':
+ return
+
+ soup_title = BeautifulSoup(page.title.replace('&nbsp;', ' '), 'html.parser')
+ page_title = soup_title.get_text(' ', strip=True).replace('“', '"').replace('”', '"').replace('’', "'").replace('^', '&#94;')
+
+ soup_text = BeautifulSoup(page.content, 'html.parser')
+ page_text = soup_text.get_text(' ', strip=True).replace('“', '"').replace('”', '"').replace('’', "'").replace('¶', ' ').replace('^', '&#94;')
+ page_text = ' '.join(page_text.split())
+
+ if getattr(page, 'category', 'None') == 'None':
+ page_category = ''
+ else:
+ page_category = page.category.name
+
+ page_url = self.siteurl + '/' + page.url
+
+ node = {'title': page_title,
+ 'text': page_text,
+ 'tags': page_category,
+ 'url': page_url,
+ 'loc': page_url}
+
+ self.json_nodes.append(node)
+
+
+ def create_tpage_node(self, srclink):
+
+ srcfile = open(os.path.join(self.output_path, self.tpages[srclink]), encoding='utf-8')
+ soup = BeautifulSoup(srcfile, 'html.parser')
+ page_text = soup.get_text()
+
+ # What happens if there is not a title.
+ if soup.title is not None:
+ page_title = soup.title.string
+ else:
+ page_title = ''
+
+ # Should set default category?
+ page_category = ''
+
+ page_url = urljoin(self.siteurl, self.tpages[srclink])
+
+ node = {'title': page_title,
+ 'text': page_text,
+ 'tags': page_category,
+ 'url': page_url,
+ 'loc': page_url}
+
+ self.json_nodes.append(node)
+
+
+ def generate_output(self, writer):
+ path = os.path.join(self.output_path, 'tipuesearch_content.json')
+
+ pages = self.context['pages'] + self.context['articles']
+
+ for article in self.context['articles']:
+ pages += article.translations
+
+ for srclink in self.tpages:
+ self.create_tpage_node(srclink)
+
+ for page in pages:
+ self.create_json_node(page)
+ root_node = {'pages': self.json_nodes}
+
+ with open(path, 'w', encoding='utf-8') as fd:
+ json.dump(root_node, fd, separators=(',', ':'), ensure_ascii=False)
+
+
+def get_generators(generators):
+ return Tipue_Search_JSON_Generator
+
+
+def register():
+ signals.get_generators.connect(get_generators)
diff --git a/spice-space/static/tipuesearch/img/loader.gif b/spice-space/static/tipuesearch/img/loader.gif
new file mode 100644
index 0000000..9c97738
--- /dev/null
+++ b/spice-space/static/tipuesearch/img/loader.gif
Binary files differ
diff --git a/spice-space/static/tipuesearch/img/search.png b/spice-space/static/tipuesearch/img/search.png
new file mode 100644
index 0000000..9ab0f2c
--- /dev/null
+++ b/spice-space/static/tipuesearch/img/search.png
Binary files differ
diff --git a/spice-space/static/tipuesearch/tipuesearch.css b/spice-space/static/tipuesearch/tipuesearch.css
new file mode 100644
index 0000000..bb03631
--- /dev/null
+++ b/spice-space/static/tipuesearch/tipuesearch.css
@@ -0,0 +1,134 @@
+/*
+Tipue Search 3.1
+Copyright (c) 2013 Tipue
+Tipue Search is released under the MIT License
+http://www.tipue.com/search
+*/
+#tipue_search_content
+{
+ clear: left;
+ margin: 0;
+ max-width: 650px;
+ padding: 25px 0 13px 0;
+}
+#tipue_search_loading
+{
+ background: #fff url('img/loader.gif') no-repeat left;
+ padding-top: 60px;
+}
+#tipue_search_warning
+{
+ color: #333;
+ font: 12px/1.6 'Helvetica Neue', Helvetica, 'Gill Sans', 'Gill Sans MT', Calibri, Arial, Sans-Serif;
+ margin: 7px 0;
+}
+#tipue_search_warning a
+{
+ color: #3f72d8;
+ text-decoration: none;
+}
+#tipue_search_warning a:hover
+{
+ border-bottom: 1px solid #ccc;
+ padding-bottom: 1px;
+}
+#tipue_search_results_count, #tipue_search_warning_head
+{
+ border-bottom: 1px solid rgb(143, 134, 134);
+ color: darkred;
+ font-family: Baskerville, Garamond, Georgia, "DejaVu Serif", "Times New Roman", Times, Serif;
+ font-size: 3em;
+ font-weight: normal;
+ margin: 20px 0 20px 0;
+ padding: 5px 5px 15px 5px;
+}
+.tipue_search_content_title
+{
+ font: 300 23px/1.6 'Helvetica Neue', Helvetica, 'Gill Sans', 'Gill Sans MT', Calibri, Arial, Sans-Serif;
+ margin-top: 20px;
+}
+.tipue_search_content_title a
+{
+ color: #3f72d8;
+ text-decoration: none;
+}
+.tipue_search_content_title a:hover
+{
+ border-bottom: 1px solid #ccc;
+ padding-bottom: 1px;
+}
+.tipue_search_content_text
+{
+ color: #333;
+ font-family: Scala, Georgia, "DejaVu Serif", "Times New Roman", Times, Serif;
+ font-size: 1em;
+ font-variant: normal;
+ line-height: 1.6em;
+ padding: 13px 0;
+ text-transform: none;
+}
+
+.tipue_search_content_text b {
+ color: #EF1D1D;
+ font-family: Garamond;
+ font-size: 1.3em;
+}
+.tipue_search_content_loc
+{
+ font: 300 13px/1.7 'Helvetica Neue', Helvetica, 'Gill Sans', 'Gill Sans MT', Calibri, Arial, Sans-Serif;
+ overflow: auto;
+}
+.tipue_search_content_loc a
+{
+ color: #555;
+ text-decoration: none;
+}
+.tipue_search_content_loc a:hover
+{
+ border-bottom: 1px solid #ccc;
+ padding-bottom: 1px;
+}
+#tipue_search_foot
+{
+ margin: 51px 0 21px 0;
+}
+#tipue_search_foot_boxes
+{
+ font: 12px/1 'Helvetica Neue', Helvetica, 'Gill Sans', 'Gill Sans MT', Calibri, Arial, Sans-Serif;
+ margin: 0;
+ padding: 0;
+}
+#tipue_search_foot_boxes li
+{
+ display: inline;
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+#tipue_search_foot_boxes li a
+{
+ background-color: #f1f1f1;
+ border-radius: 1px;
+ border: 1px solid #dcdcdc;
+ color: #333;
+ margin-right: 7px;
+ padding: 7px 13px 8px 13px;
+ text-align: center;
+ text-decoration: none;
+}
+#tipue_search_foot_boxes li.current
+{
+ background: #fff;
+ border-radius: 1px;
+ border: 1px solid #dcdcdc;
+ color: #333;
+ margin-right: 7px;
+ padding: 7px 13px 8px 13px;
+ text-align: center;
+}
+#tipue_search_foot_boxes li a:hover
+{
+ background-color: #f3f3f3;
+ border: 1px solid #ccc;
+}
+
diff --git a/spice-space/static/tipuesearch/tipuesearch.js b/spice-space/static/tipuesearch/tipuesearch.js
new file mode 100644
index 0000000..1677795
--- /dev/null
+++ b/spice-space/static/tipuesearch/tipuesearch.js
@@ -0,0 +1,372 @@
+
+/*
+Tipue Search 3.1
+Copyright (c) 2013 Tipue
+Tipue Search is released under the MIT License
+http://www.tipue.com/search
+*/
+
+
+(function($) {
+
+ $.fn.tipuesearch = function(options) {
+
+ var set = $.extend( {
+ 'show' : 7,
+ 'newWindow' : false,
+ 'showURL' : true,
+ 'minimumLength' : 3,
+ 'descriptiveWords' : 25,
+ 'highlightTerms' : true,
+ 'highlightEveryTerm' : false,
+ 'mode' : 'static',
+ 'liveDescription' : '*',
+ 'liveContent' : '*',
+ 'contentLocation' : 'tipuesearch/tipuesearch_content.json'
+ }, options);
+ return this.each(function() {
+
+ var tipuesearch_in = {
+ pages: []
+ };
+ $.ajaxSetup({
+ async: false
+ });
+
+ if (set.mode == 'live')
+ {
+ for (var i = 0; i < tipuesearch_pages.length; i++)
+ {
+ $.get(tipuesearch_pages[i], '',
+ function (html)
+ {
+ var cont = $(set.liveContent, html).text();
+ cont = cont.replace(/\s+/g, ' ');
+ var desc = $(set.liveDescription, html).text();
+ desc = desc.replace(/\s+/g, ' ');
+ var t_1 = html.toLowerCase().indexOf('<title>');
+ var t_2 = html.toLowerCase().indexOf('</title>', t_1 + 7);
+ if (t_1 != -1 && t_2 != -1)
+ {
+ var tit = html.slice(t_1 + 7, t_2);
+ }
+ else
+ {
+ var tit = 'No title';
+ }
+
+ tipuesearch_in.pages.push({
+ "title": tit,
+ "text": desc,
+ "tags": cont,
+ "loc": tipuesearch_pages[i]
+ });
+ }
+ );
+ }
+ }
+ if (set.mode == 'json')
+ {
+ $.getJSON(set.contentLocation,
+ function(json)
+ {
+ tipuesearch_in = $.extend({}, json);
+ }
+ );
+ }
+
+ if (set.mode == 'static')
+ {
+ tipuesearch_in = $.extend({}, tipuesearch);
+ }
+ var tipue_search_w = '';
+ if (set.newWindow)
+ {
+ tipue_search_w = ' target="_blank"';
+ }
+
+ function getURLP(name)
+ {
+ return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search)||[,""])[1].replace(/\+/g, '%20')) || null;
+ }
+ if (getURLP('q'))
+ {
+ $('#tipue_search_input').val(getURLP('q'));
+ getTipueSearch(0, true);
+ }
+
+ $('#tipue_search_button').click(function()
+ {
+ getTipueSearch(0, true);
+ });
+ $(this).keyup(function(event)
+ {
+ if(event.keyCode == '13')
+ {
+ getTipueSearch(0, true);
+ }
+ });
+
+ function getTipueSearch(start, replace)
+ {
+ $('#tipue_search_content').hide();
+ var out = '';
+ var results = '';
+ var show_replace = false;
+ var show_stop = false;
+
+ var d = $('#tipue_search_input').val().toLowerCase();
+ d = $.trim(d);
+ var d_w = d.split(' ');
+ d = '';
+ for (var i = 0; i < d_w.length; i++)
+ {
+ var a_w = true;
+ for (var f = 0; f < tipuesearch_stop_words.length; f++)
+ {
+ if (d_w[i] == tipuesearch_stop_words[f])
+ {
+ a_w = false;
+ show_stop = true;
+ }
+ }
+ if (a_w)
+ {
+ d = d + ' ' + d_w[i];
+ }
+ }
+ d = $.trim(d);
+ d_w = d.split(' ');
+
+ if (d.length >= set.minimumLength)
+ {
+ if (replace)
+ {
+ var d_r = d;
+ for (var i = 0; i < d_w.length; i++)
+ {
+ for (var f = 0; f < tipuesearch_replace.words.length; f++)
+ {
+ if (d_w[i] == tipuesearch_replace.words[f].word)
+ {
+ d = d.replace(d_w[i], tipuesearch_replace.words[f].replace_with);
+ show_replace = true;
+ }
+ }
+ }
+ d_w = d.split(' ');
+ }
+
+ var d_t = d;
+ for (var i = 0; i < d_w.length; i++)
+ {
+ for (var f = 0; f < tipuesearch_stem.words.length; f++)
+ {
+ if (d_w[i] == tipuesearch_stem.words[f].word)
+ {
+ d_t = d_t + ' ' + tipuesearch_stem.words[f].stem;
+ }
+ }
+ }
+ d_w = d_t.split(' ');
+
+ var c = 0;
+ found = new Array();
+ for (var i = 0; i < tipuesearch_in.pages.length; i++)
+ {
+ var score = 1000000000;
+ var s_t = tipuesearch_in.pages[i].text;
+ for (var f = 0; f < d_w.length; f++)
+ {
+ var pat = new RegExp(d_w[f], 'i');
+ if (tipuesearch_in.pages[i].title.search(pat) != -1)
+ {
+ score -= (200000 - i);
+ }
+ if (tipuesearch_in.pages[i].text.search(pat) != -1)
+ {
+ score -= (150000 - i);
+ }
+
+ if (set.highlightTerms)
+ {
+ if (set.highlightEveryTerm)
+ {
+ var patr = new RegExp('(' + d_w[f] + ')', 'gi');
+ }
+ else
+ {
+ var patr = new RegExp('(' + d_w[f] + ')', 'i');
+ }
+ s_t = s_t.replace(patr, "<b>$1</b>");
+ }
+ if (tipuesearch_in.pages[i].tags.search(pat) != -1)
+ {
+ score -= (100000 - i);
+ }
+ }
+ if (score < 1000000000)
+ {
+ found[c++] = score + '^' + tipuesearch_in.pages[i].title + '^' + s_t + '^' + tipuesearch_in.pages[i].loc;
+ }
+ }
+ if (c != 0)
+ {
+ if (show_replace == 1)
+ {
+ out += '<div id="tipue_search_warning_head">Showing results for ' + d + '</div>';
+ out += '<div id="tipue_search_warning">Search for <a href="javascript:void(0)" id="tipue_search_replaced">' + d_r + '</a></div>';
+ }
+ if (c == 1)
+ {
+ out += '<div id="tipue_search_results_count">1 result</div>';
+ }
+ else
+ {
+ c_c = c.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+ out += '<div id="tipue_search_results_count">' + c_c + ' results</div>';
+ }
+ found.sort();
+ var l_o = 0;
+ for (var i = 0; i < found.length; i++)
+ {
+ var fo = found[i].split('^');
+ if (l_o >= start && l_o < set.show + start)
+ {
+ out += '<div class="tipue_search_content_title"><a href="' + fo[3] + '"' + tipue_search_w + '>' + fo[1] + '</a></div>';
+ var t = fo[2];
+ var t_d = '';
+ var t_w = t.split(' ');
+ if (t_w.length < set.descriptiveWords)
+ {
+ t_d = t;
+ }
+ else
+ {
+ for (var f = 0; f < set.descriptiveWords; f++)
+ {
+ t_d += t_w[f] + ' ';
+ }
+ }
+ t_d = $.trim(t_d);
+ if (t_d.charAt(t_d.length - 1) != '.')
+ {
+ t_d += ' ...';
+ }
+ out += '<div class="tipue_search_content_text">' + t_d + '</div>';
+ if (set.showURL)
+ {
+ out += '<div class="tipue_search_content_loc"><a href="' + fo[3] + '"' + tipue_search_w + '>' + fo[3] + '</a></div>';
+ }
+ }
+ l_o++;
+ }
+
+ if (c > set.show)
+ {
+ var pages = Math.ceil(c / set.show);
+ var page = (start / set.show);
+ out += '<div id="tipue_search_foot"><ul id="tipue_search_foot_boxes">';
+
+ if (start > 0)
+ {
+ out += '<li><a href="javascript:void(0)" class="tipue_search_foot_box" id="' + (start - set.show) + '_' + replace + '">&#171; Prev</a></li>';
+ }
+
+ if (page <= 2)
+ {
+ var p_b = pages;
+ if (pages > 3)
+ {
+ p_b = 3;
+ }
+ for (var f = 0; f < p_b; f++)
+ {
+ if (f == page)
+ {
+ out += '<li class="current">' + (f + 1) + '</li>';
+ }
+ else
+ {
+ out += '<li><a href="javascript:void(0)" class="tipue_search_foot_box" id="' + (f * set.show) + '_' + replace + '">' + (f + 1) + '</a></li>';
+ }
+ }
+ }
+ else
+ {
+ var p_b = pages + 2;
+ if (p_b > pages)
+ {
+ p_b = pages;
+ }
+ for (var f = page; f < p_b; f++)
+ {
+ if (f == page)
+ {
+ out += '<li class="current">' + (f + 1) + '</li>';
+ }
+ else
+ {
+ out += '<li><a href="javascript:void(0)" class="tipue_search_foot_box" id="' + (f * set.show) + '_' + replace + '">' + (f + 1) + '</a></li>';
+ }
+ }
+ }
+
+ if (page + 1 != pages)
+ {
+ out += '<li><a href="javascript:void(0)" class="tipue_search_foot_box" id="' + (start + set.show) + '_' + replace + '">Next &#187;</a></li>';
+ }
+
+ out += '</ul></div>';
+ }
+ }
+ else
+ {
+ out += '<div id="tipue_search_warning_head">Nothing found</div>';
+ }
+ }
+ else
+ {
+ if (show_stop)
+ {
+ out += '<div id="tipue_search_warning_head">Nothing found</div><div id="tipue_search_warning">Common words are largely ignored</div>';
+ }
+ else
+ {
+ out += '<div id="tipue_search_warning_head">Search too short</div>';
+ if (set.minimumLength == 1)
+ {
+ out += '<div id="tipue_search_warning">Should be one character or more</div>';
+ }
+ else
+ {
+ out += '<div id="tipue_search_warning">Should be ' + set.minimumLength + ' characters or more</div>';
+ }
+ }
+ }
+
+ $('#tipue_search_content').html(out);
+ $('#tipue_search_content').slideDown(200);
+
+ $('#tipue_search_replaced').click(function()
+ {
+ getTipueSearch(0, false);
+ });
+
+ $('.tipue_search_foot_box').click(function()
+ {
+ var id_v = $(this).attr('id');
+ var id_a = id_v.split('_');
+
+ getTipueSearch(parseInt(id_a[0]), id_a[1]);
+ });
+ }
+
+ });
+ };
+
+})(jQuery);
+
+
+
+
diff --git a/spice-space/static/tipuesearch/tipuesearch.min.js b/spice-space/static/tipuesearch/tipuesearch.min.js
new file mode 100644
index 0000000..bfc66f1
--- /dev/null
+++ b/spice-space/static/tipuesearch/tipuesearch.min.js
@@ -0,0 +1,12 @@
+(function($){$.fn.tipuesearch=function(options){var set=$.extend({"show":7,"newWindow":false,"showURL":true,"minimumLength":3,"descriptiveWords":25,"highlightTerms":true,"highlightEveryTerm":false,"mode":"static","liveDescription":"*","liveContent":"*","contentLocation":"tipuesearch/tipuesearch_content.json"},options);return this.each(function(){var tipuesearch_in={pages:[]};$.ajaxSetup({async:false});if(set.mode=="live")for(var i=0;i<tipuesearch_pages.length;i++)$.get(tipuesearch_pages[i],"",function(html){var cont=
+$(set.liveContent,html).text();cont=cont.replace(/\s+/g," ");var desc=$(set.liveDescription,html).text();desc=desc.replace(/\s+/g," ");var t_1=html.toLowerCase().indexOf("<title>");var t_2=html.toLowerCase().indexOf("</title>",t_1+7);if(t_1!=-1&&t_2!=-1)var tit=html.slice(t_1+7,t_2);else var tit="No title";tipuesearch_in.pages.push({"title":tit,"text":desc,"tags":cont,"loc":tipuesearch_pages[i]})});if(set.mode=="json")$.getJSON(set.contentLocation,function(json){tipuesearch_in=$.extend({},json)});
+if(set.mode=="static")tipuesearch_in=$.extend({},tipuesearch);var tipue_search_w="";if(set.newWindow)tipue_search_w=' target="_blank"';function getURLP(name){return decodeURIComponent(((new RegExp("[?|&]"+name+"="+"([^&;]+?)(&|#|;|$)")).exec(location.search)||[,""])[1].replace(/\+/g,"%20"))||null}if(getURLP("q")){$("#tipue_search_input").val(getURLP("q"));getTipueSearch(0,true)}$("#tipue_search_button").click(function(){getTipueSearch(0,true)});$(this).keyup(function(event){if(event.keyCode=="13")getTipueSearch(0,
+true)});function getTipueSearch(start,replace){$("#tipue_search_content").hide();var out="";var results="";var show_replace=false;var show_stop=false;var d=$("#tipue_search_input").val().toLowerCase();d=$.trim(d);var d_w=d.split(" ");d="";for(var i=0;i<d_w.length;i++){var a_w=true;for(var f=0;f<tipuesearch_stop_words.length;f++)if(d_w[i]==tipuesearch_stop_words[f]){a_w=false;show_stop=true}if(a_w)d=d+" "+d_w[i]}d=$.trim(d);d_w=d.split(" ");if(d.length>=set.minimumLength){if(replace){var d_r=d;for(var i=
+0;i<d_w.length;i++)for(var f=0;f<tipuesearch_replace.words.length;f++)if(d_w[i]==tipuesearch_replace.words[f].word){d=d.replace(d_w[i],tipuesearch_replace.words[f].replace_with);show_replace=true}d_w=d.split(" ")}var d_t=d;for(var i=0;i<d_w.length;i++)for(var f=0;f<tipuesearch_stem.words.length;f++)if(d_w[i]==tipuesearch_stem.words[f].word)d_t=d_t+" "+tipuesearch_stem.words[f].stem;d_w=d_t.split(" ");var c=0;found=new Array;for(var i=0;i<tipuesearch_in.pages.length;i++){var score=1E9;var s_t=tipuesearch_in.pages[i].text;
+for(var f=0;f<d_w.length;f++){var pat=new RegExp(d_w[f],"i");if(tipuesearch_in.pages[i].title.search(pat)!=-1)score-=2E5-i;if(tipuesearch_in.pages[i].text.search(pat)!=-1)score-=15E4-i;if(set.highlightTerms){if(set.highlightEveryTerm)var patr=new RegExp("("+d_w[f]+")","gi");else var patr=new RegExp("("+d_w[f]+")","i");s_t=s_t.replace(patr,"<b>$1</b>")}if(tipuesearch_in.pages[i].tags.search(pat)!=-1)score-=1E5-i}if(score<1E9)found[c++]=score+"^"+tipuesearch_in.pages[i].title+"^"+s_t+"^"+tipuesearch_in.pages[i].loc}if(c!=
+0){if(show_replace==1){out+='<div id="tipue_search_warning_head">Showing results for '+d+"</div>";out+='<div id="tipue_search_warning">Search for <a href="javascript:void(0)" id="tipue_search_replaced">'+d_r+"</a></div>"}if(c==1)out+='<div id="tipue_search_results_count">1 result</div>';else{c_c=c.toString().replace(/\B(?=(\d{3})+(?!\d))/g,",");out+='<div id="tipue_search_results_count">'+c_c+" results</div>"}found.sort();var l_o=0;for(var i=0;i<found.length;i++){var fo=found[i].split("^");if(l_o>=
+start&&l_o<set.show+start){out+='<div class="tipue_search_content_title"><a href="'+fo[3]+'"'+tipue_search_w+">"+fo[1]+"</a></div>";var t=fo[2];var t_d="";var t_w=t.split(" ");if(t_w.length<set.descriptiveWords)t_d=t;else for(var f=0;f<set.descriptiveWords;f++)t_d+=t_w[f]+" ";t_d=$.trim(t_d);if(t_d.charAt(t_d.length-1)!=".")t_d+=" ...";out+='<div class="tipue_search_content_text">'+t_d+"</div>";if(set.showURL)out+='<div class="tipue_search_content_loc"><a href="'+fo[3]+'"'+tipue_search_w+">"+fo[3]+
+"</a></div>"}l_o++}if(c>set.show){var pages=Math.ceil(c/set.show);var page=start/set.show;out+='<div id="tipue_search_foot"><ul id="tipue_search_foot_boxes">';if(start>0)out+='<li><a href="javascript:void(0)" class="tipue_search_foot_box" id="'+(start-set.show)+"_"+replace+'">&#171; Prev</a></li>';if(page<=2){var p_b=pages;if(pages>3)p_b=3;for(var f=0;f<p_b;f++)if(f==page)out+='<li class="current">'+(f+1)+"</li>";else out+='<li><a href="javascript:void(0)" class="tipue_search_foot_box" id="'+f*set.show+
+"_"+replace+'">'+(f+1)+"</a></li>"}else{var p_b=pages+2;if(p_b>pages)p_b=pages;for(var f=page;f<p_b;f++)if(f==page)out+='<li class="current">'+(f+1)+"</li>";else out+='<li><a href="javascript:void(0)" class="tipue_search_foot_box" id="'+f*set.show+"_"+replace+'">'+(f+1)+"</a></li>"}if(page+1!=pages)out+='<li><a href="javascript:void(0)" class="tipue_search_foot_box" id="'+(start+set.show)+"_"+replace+'">Next &#187;</a></li>';out+="</ul></div>"}}else out+='<div id="tipue_search_warning_head">Nothing found</div>'}else if(show_stop)out+=
+'<div id="tipue_search_warning_head">Nothing found</div><div id="tipue_search_warning">Common words are largely ignored</div>';else{out+='<div id="tipue_search_warning_head">Search too short</div>';if(set.minimumLength==1)out+='<div id="tipue_search_warning">Should be one character or more</div>';else out+='<div id="tipue_search_warning">Should be '+set.minimumLength+" characters or more</div>"}$("#tipue_search_content").html(out);$("#tipue_search_content").slideDown(200);$("#tipue_search_replaced").click(function(){getTipueSearch(0,
+false)});$(".tipue_search_foot_box").click(function(){var id_v=$(this).attr("id");var id_a=id_v.split("_");getTipueSearch(parseInt(id_a[0]),id_a[1])})}})}})(jQuery);
diff --git a/spice-space/static/tipuesearch/tipuesearch_set.js b/spice-space/static/tipuesearch/tipuesearch_set.js
new file mode 100644
index 0000000..ea4abd6
--- /dev/null
+++ b/spice-space/static/tipuesearch/tipuesearch_set.js
@@ -0,0 +1,52 @@
+
+/*
+Tipue Search 3.1
+Copyright (c) 2013 Tipue
+Tipue Search is released under the MIT License
+http://www.tipue.com/search
+*/
+
+
+var tipuesearch_stop_words = ["and", "be", "by", "do", "for", "he", "how", "if", "is", "it", "my", "not", "of", "or", "the", "to", "up", "what", "when"];
+
+var tipuesearch_replace = {"words": [
+ {"word": "tipua", replace_with: "tipue"},
+ {"word": "javscript", replace_with: "javascript"}
+]};
+
+var tipuesearch_stem = {"words": [
+ {"word": "e-mail", stem: "email"},
+ {"word": "javascript", stem: "script"},
+ {"word": "javascript", stem: "js"}
+]};
+
+var tipuesearch_pages;
+
+exclude_pages = ['/archives.html', '/tags.html', '/index.html', '/categories.html', '/search.html'];
+
+function showGetResult()
+{
+ var result = new Array();
+ var scriptUrl = 'sitemap.xml';
+ $.ajax({
+ url: scriptUrl,
+ type: 'GET',
+ dataType: 'xml',
+ async: false,
+ success: function(xml) {
+ $(xml).find('url').each(function(){
+ var loc = $(this).find('loc').text();
+ if ($.inArray(loc, exclude_pages) < 0) {
+ result.push(loc);
+ }
+ });
+ },
+ error: function() {
+ alert('An error occurred while processing XML file.');
+ }
+ });
+ return result;
+}
+
+var r = showGetResult();
+tipuesearch_pages = r;
diff --git a/spice-space/templates/base.html b/spice-space/templates/base.html
index d1e6552..eb73ed4 100644
--- a/spice-space/templates/base.html
+++ b/spice-space/templates/base.html
@@ -4,13 +4,23 @@
<meta charset="utf-8" />
<title>{% block title %}{{ SITENAME }}{%endblock%}</title>
<link rel="stylesheet" href="{{ SITEURL }}/{{ THEME_STATIC_DIR }}/css/{{ CSS_FILE }}" />
+ <link rel="stylesheet" type="text/css" href="{{ SITEURL }}/theme/tipuesearch/tipuesearch.css" media="screen">
{% if FEED_ALL_ATOM %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_ATOM }}" type="application/atom+xml" rel="alternate" title="{{ SITENAME }} Atom Feed" />
{% endif %}
{% if FEED_ALL_RSS %}
<link href="{{ FEED_DOMAIN }}/{{ FEED_ALL_RSS }}" type="application/rss+xml" rel="alternate" title="{{ SITENAME }} RSS Feed" />
{% endif %}
-
+ {% block script %}
+ <script src="http://code.jquery.com/jquery.min.js"></script>
+ <script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.2/js/bootstrap.min.js"></script>
+ <script>
+ function validateForm(query)
+ {
+ return (query.length > 0);
+ }
+ </script>
+ {% endblock script %}
<!--[if IE]>
<script src="https://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
@@ -20,9 +30,13 @@
{% include 'github.html' %}
<header id="banner" class="body">
<div id="search-area">
- <form action="http://www.google.com/search" method="get">
- Search: <input class="search-box" id="q" name="q" type="text"/>
- <input type="hidden" name="sitesearch" value="spice-space.org"/>
+ <form action="{{ SITEURL }}/search.html"
+ onsubmit="return validateForm(this.elements['q'].value);">
+ <input type="text"
+ class="search-query"
+ placeholder="Search"
+ name="q"
+ id="tipue_search_input">
</form>
</div>
<nav>
@@ -73,7 +87,6 @@
</a>
</div>
</footer><!-- /#contentinfo -->
-
{% include 'analytics.html' %}
{% include 'disqus_script.html' %}
</body>
diff --git a/spice-space/templates/search.html b/spice-space/templates/search.html
new file mode 100644
index 0000000..8bd3839
--- /dev/null
+++ b/spice-space/templates/search.html
@@ -0,0 +1,26 @@
+{% extends 'base.html' %}
+
+{% block script %}
+<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
+<script type="text/javascript" src="{{ SITEURL }}/theme/tipuesearch/tipuesearch_set.js"></script>
+<script type="text/javascript" src="{{ SITEURL }}/theme/tipuesearch/tipuesearch.js"></script>
+<script>
+$(document).ready(function() {
+ $('#tipue_search_input').tipuesearch({
+ 'mode' : 'json',
+ 'show': 10,
+ 'newWindow': false,
+ 'contentLocation': 'tipuesearch_content.json'
+ });
+});
+</script>
+{% endblock script %}
+
+{% block content %}
+<section id="content" class="body">
+ <h1 class="entry-title">Search</h1>
+ <div class="span8 offset2">
+ <div id="tipue_search_content"><div id="tipue_search_loading"></div></div>
+ </div>
+</section>
+{% endblock content %}