с некоторым удивлением я узнал, что в некоторых программах можно использовать колёсико мыши для горизонтальной прокрутки, удерживая Shift. Очень удобная фича, которую мне немедленно захотелось иметь в своём браузере. Сейчас я использую для этого Panning, который включается кликом средней кнопки, но у него есть свои недостатки. Во-первый, он довольно медленный (или слишком быстрый, если двигать мышь неаккуратно). Во-вторых, можно случайно кликнуть по ссылке или полю ввода, и получить неожиданный результат.
к сожалению, это сочетание кнопок в браузерах уже зарезервировано под переходы вперёд/назад по истории. К ещё большему сожалению, в опере это внезапно! нельзя отключить. И, наконец, в опере это событие нельзя предотвратить яваскриптом (DSK-351368). Так что пришлось использовать Alt, что тоже неплохо. А вот и код:
document.addEventListener(
'mousewheel',
function(event){
if (!event.altKey) return;
for (
var target = event.target;
target;
target = target.parentNode)
{
if (target.scrollWidth <= target.offsetWidth) continue;
target.scrollLeft -= event.wheelDelta;
event.preventDefault();
break;
}
},
false);
во многих разных фантастических книжках и фильмах компьютер персонажа действует самостоятельно, изучая данные в сети и отбирая из них то, что нужно его владельцу. Иными словами, компьютер там работает как доверенный агент человека, почти не обременённый интеллектом, но способный на самостоятельность. К сожалению или к счастью, в реальном мире обычные люди практически не используют компьютеры таким образом.
некоторое приближение к фантастике можно увидеть в том, как программы-аггрегаторы обходят сайты, на которые подписан владелец, и собирают с них обновления в виде atom/rss. Однако самостоятельности тут почти нет: я не знаю программ, которые хотя бы переходили по ссылкам в потоках, не говоря уже о ссылках второго уровня. Вроде бы есть аггрегаторы, которые могут показать не взятый из atom/rss текст, а страницу, на которую он ссылается, но обычно результат от этого становится только хуже: на странице практически всегда присутствует очень много лишних элементов. Хотя понятно, что таким образом они пытаются решить проблему «в atom/rss присутствует только анонс текста».
поскольку у меня есть браузер с широкими возможностями пользовательских скриптов и, по сути, собственная читалка atom/rss, ничто не помешало мне разобраться с этой проблемой любимым способом. Читалке я сказал, какие потоки содержат только анонсы. За один вечер я научил её открывать ссылки из этих потоков в ифреймах, вынимать из загруженных страниц интересующие меня теги (для каждого сайта — свой css-селектор), и показывать их прямо в интерфейсе читалки вместо анонсов. Получился примитивный агент, выбирающий для меня только нужный мне текст вопреки огораживанию
кстати, на очевидное возражение «сайтам тоже нужно зарабатывать» контраргумент настолько же очевиден: «компьютер должен служить своему хозяину»
в новом интерфейсе ридера возможности рекомендовать записи уже нет, но в API она пока ещё работает. Однако вполне вероятно, что скоро её в ридере не будет вообще. Исполненный этих мрачных предчувствий, я решил построить свой «шаринг», с блекджеком WebDAV и incron.
выбор технологии обусловила моя нездоровая (?) любовь к статическим файлам, которые лежат себе мирно на диске и ведут себя предсказуемо, в отличие от некоторых. Например, скопировав себе всю историю своих рекомендаций, я разбил её на отдельные файлики, каждый из которых содержит один xml-элемент entry из потока, а имя файла соответствует названию записи. Ну и для удобства обращения с 11к файлов я разбил их на папки по месяцам, и установил временем модификации файла момент, когда я рекомендовал эту запись.
такой способ хранения прямо-таки напрашивается на создание потока простой конкатенацией. Нужно только как-то добавить очередной файл в нужный каталог и склеить двадцать последних файлов с префиксом. Для добавления можно использовать встроенную в модный nginx поддержку WebDAV, с которой мне давно хотелось поиграться. А запускать скрипт по изменению в файловой системе может ещё более модный incron.
к сожалению, на виртуальном сервере incron работал не идеально, поэтому на ровном месте пришлось хорошо побиться лбом о стену. Однако удалось добиться того, чтобы при загрузке файла в каталог или его удалении запускался нужный скрипт. Встроенная защита от рекурсии почему-то не работала, пришлось полагаться на старый добрый lock-файл. На всякий случай приведу здесь единственную строчку конфигурации incron:
inbox IN_DELETE,IN_MOVED_TO new-entry.bash
со скриптом тоже внезапно пришлось повозиться — «спасибо» башу за удобство работы с файлами, в имени которых есть пробелы. Вообще-то, сначала я собирался избежать этой проблемы, написав скрипт на питоне, но не хотелось возиться в нём с сортировкой файлов по времени создания. Хорошо, что задача довольно простая: скопировать самый свежий файл из «входящих» в правильный каталог на хранение, удалить самый старый файл во «входящих», подклеить к префиксу все файлы во «входящих»:
ну и ещё одни грабли ждали меня при попытке использовать WebDAV. Я помнил, что яваскрипт работает на www.google.com, а моё хранилище — на совсем другом домене, но собирался обойти кроссдоменное ограничение классической отправкой формы в ифрейм. Не тут-то было: обычные html-формы не умеют метод PUT, его можно использовать только в XMLHttpRequest — но он споткнётся о другой домен. Хорошо, что есть postMessage, и что я полностью контролирую свой сервер: маленький прокси меня выручил.
<!doctype html>
<title>proxy</title>
<script>
window.onmessage = function(event) {
if (!event.origin.match(/^https?:..www.google.com/)) return;
var url = encodeURIComponent(event.data.name.replace(/\//g, '-'));
var x = new XMLHttpRequest();
x.open('PUT', 'http://shared.arty.name/inbox/' + url);
x.send(event.data.content);
}
</script>
в итоге, когда я нажимаю в ридере кнопку «рекомендовать», происходит вот что:
название и текст записи отправляются в прокси-ифрейм
прокси отправляет их на url «входящих»
nginx принимает запрос и сохраняет его в файл
incron замечает изменение во «входящих» и запускает скрипт
скрипт копирует запись в архив и пересоздаёт файл с потоком рекомендаций
по-моему, здорово! : ) Сыровато ещё, нужны доделки, но уже работает, да как затейливо! : )
благодаря open google reader интерфейс google reader для меня не изменился, и возможность рекомендовать записи у меня тоже осталась. Однако комментариев в ридере скоро не станет совсем, даже приходящих из базза, поэтому хочется на всякий случай забрать из него всё своё. К счастью гугл старается быть хорошим и даёт возможность экспорта данных (Настройки → Импорт/экспорт). К несчастью, у активных пользователей вроде меня нередко скачиваются только данные за короткий период.
к счастью, гугл давно старается быть хорошим, поэтому он дал нам многостраничный atom feed. Вот страничка с моими рекомендациями и бесконечной промоткой назад при помощи параметра ?c= в адресе, а вот поток моих рекомендаций, в котором нет прямой ссылки на «предыдущую страницу», зато есть тег gr:continuation. Если его содержимое подставить в адрес потока как параметр ?c=, то результатом будут предыдущие 20 рекомендаций.
а вот простой скрипт на питоне, в который достаточно подставить id из адреса своего потока рекомендаций, и он скачает всю историю этого потока, что я с радостью и сделал для себя:
id = '10202714043885511706'
import os, urllib, xml.dom.minidom as dom
def get(continuation):
url2 = url + continuation
print url2
text = ''.join(urllib.urlopen(url2))
atom = dom.parseString(text)
continuation = atom.getElementsByTagName('gr:continuation')
continuation = continuation[0].firstChild.nodeValue
date = atom.getElementsByTagName('updated')
date = date[0].firstChild.nodeValue
folder = date[:7]
if not os.path.exists(folder): os.mkdir(folder)
f = open(os.path.join(folder, date), 'w')
f.write(text)
f.close()
return continuation
url = ('http://www.google.com/reader/public/atom/user%%2F' +\
'%s%%2Fstate%%2Fcom.google%%2Fbroadcast?c=') % id
continuation = ''
while True: continuation = get(continuation)
в качестве дополнительного бонуса у меня теперь заработает нормальный поиск по своим же рекомендациям : )