Я люблю статические блоги. Но очень не люблю то, как в них часто устроен поиск. А именно то, что в большинстве таких блогов по запросу в поисковой строке просто открывается google. В новой вкладке, если повезет. Такая ситуация меня не устраивает, и я подумал об организации нормального поиска в своем блоге на Octopress.
Перед тем, как строить свой велосипед, надо сначала изучить, какие уже придуманы.
Tapir
Tapir — бесплатный сервис, индексирующий RSS ленту Вашего блога, и отдающий результаты поиска по запросу.
Плюсы:
+ Бесплатен
+ Не навязывает свои кнопки стили
+ Быстр
+ Легко интерпретировать
Минусы:
— Не работает с русским языком
Для меня не работа с русским языком — критический недостаток, поэтому от Тапира пришлось отказаться.
Google Custom Search
Google Custom Search — система, позволяющая настроить на Вашем сайте поиск через Google. Мне очень понравился у них вид «Накладка», в котором результаты поиска открываются в модальном окне поверх сайта.
Плюсы:
+ Очень легко интерпретировать
+ Дизайн поиска настраивается из личного кабинета
Минусы:
— HTML и CSS подгружается с серверов Google. Хотя изменить с помощью CSS/JS можно.
— Поиск Google (Я не силен в SEO, поэтому там много лишнего)
— Реклама
Лично мне этот поиск тоже не подходит.
Если хочешь сделать что-то хорошо — сделай это сам.
Ибо больше готовых решений я не нашел, решил написать свою балалайку.
Итак, у меня блог на Octopress, с темой Octostrap 3 (BootStrap 3)
Идея проста: пользователь делает запрос, всплывает модальное окно (BootStrap 3) и происходит поиск запроса по atom.xml файлу. Затем результат выводится в виде записей.
По умолчанию в octopress стоит лимит на 20 записей в RSS. Если мы хотим искать по всему блогу, то лимит стоит снять, убрав
limit: 20
в atom.xml.Для удобной работы с atom.xml подключим jFeed. (Предварительно скачав его)
<script src="{{ root_url }}/javascripts/jquery.jfeed.js"></script>
Добавляем модальное окно. (Я его добавил в after_footer)
<div class="modal fade" id="searchModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 class="modal-title">Результаты поиска</h4>
</div>
<div class="modal-body">
<img src="/images/ajax-loader.gif" id="search-loading"></img>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
Где ajax-loader.gif:
Добавляем поисковой строке id:
<input class="form-control" type="text" id="query" placeholder="Поиск...">
И теперь javascript.
$("#search").submit(function( event ) {
event.preventDefault();
$(".modal-body").html('<img src="/images/ajax-loader.gif" id="search-loading"></img>');
var string = $("#query").val();
// При пустом запросе и при запросе, в котором менее 4 символов, выводим сообщение об ошибке.
if (string.length == 0) {
$('#searchModal').modal('show');
$(".modal-body").html('<div class="alert alert-danger">Пустой запрос</div>');
return;
}
if (string.length < 4) {
$('#searchModal').modal('show');
$(".modal-body").html('<div class="alert alert-danger">Слишком корткий запрос</div>');
return;
}
var html = "";
$('#searchModal').modal('show');
// Получаем RSS
jQuery.getFeed({
url: '{{ root_url }}/atom.xml',
success: function(feed) {
for(var i = 0; i < feed.items.length; i++) {
var item = feed.items[i];
// Убираем все html теги.
item.description = item.description.replace(/\<.*?>/g, '');
// Ищем по описанию и названию
if (item.description.toLowerCase().indexOf(string.toLowerCase()) !== -1 || item.title.toLowerCase().indexOf(string.toLowerCase()) !== -1) {
html += '<div class="panel panel-default search-panel"> <div class="panel-body"> <div class="entry-title"><h3><a href="' + item.link + '">' + item.title + '</a></h3></div>';
// Если ключевое слово встречается в описании, выводим его + по 30 символов слева/справа от него. Причем делаем ключевое слово выделенным
if (item.description.toLowerCase().indexOf(string.toLowerCase()) !== -1) {
html += '<hr /><div>...' + item.description.substring(item.description.toLowerCase().indexOf(string.toLowerCase()) -30 ,item.description.toLowerCase().indexOf(string.toLowerCase())) + '<strong>' + item.description.substring(item.description.toLowerCase().indexOf(string.toLowerCase()),item.description.toLowerCase().indexOf(string.toLowerCase()) + string.length) + '</strong>' + item.description.substring(item.description.toLowerCase().indexOf(string.toLowerCase()) + string.length,item.description.toLowerCase().indexOf(string.toLowerCase()) + string.length + 30) + '...</div>';
}
// Показываем в углу дату
html += '<p class="text-muted"><span class="glyphicon glyphicon-calendar"></span>' + dateParse(item.updated) + '</p></div></div>'
}
}
if (html == "") {
html += '<div class="alert alert-warning">Ничего не найдено</div>';
}
$(".modal-body").html(html);
}
});
});
Вспомогательная функция, которая возвращает дату в людском формате.
function dateParse(date) {
var year = date.substring(0,4);
var month = date.substring(5,7);
var day = date.substring(8,10);
switch (month) {
case '01':
month = 'Января '
break
case '02':
month = 'Февраля '
break
case '03':
month = 'Марта '
break
case '04':
month = 'Апреля '
break
case '05':
month = 'Мая '
break
case '06':
month = 'Июня '
break
case '07':
month = 'Июля '
break
case '08':
month = 'Августа '
break
case '09':
month = 'Сентября '
break
case '10':
month = 'Октября '
break
case '11':
month = 'Ноября '
break
case '12':
month = 'Декабря '
break
}
switch (day) {
case '3':
day = day + 'ее '
break
default:
day = day + 'ое '
}
return day + month + year + ' г.';
}
Немного CSS:
.search-panel .text-muted {
float: right;
padding-top: 10px;
}
.search-panel h3 {
margin-top: 0;
}
#search-loading {
display: block;
margin: 0 auto;
}
Вот что получилось в итоге:
Конечно не хватает семантики, но мне пока такое решение нравится.