Статьи

Расширение браузера: сортировка записей Tumblr в хронологическом порядке

Создание небольших расширений для браузера — отличный способ изучить JavaScript. Вам не нужно беспокоиться о кросс-браузерной совместимости, вы можете сделать проект таким маленьким или большим, как вам нравится, и вы можете исправлять проблемы на сайтах других людей, которые вас, в частности, вызывают. В этом посте мы создадим короткий скрипт, который автоматически переставит страницы архива Tumblr для чтения в хронологическом порядке — и мы узнаем о NodeList в пути.


На этой неделе Райан Норт запустил новый Tumblr , в котором он проводит романизацию первого фильма «Назад в будущее», по одной странице за раз. Это то, что я хочу прочитать.

BtotheF Нажмите, чтобы прочитать.

Тем не менее, Tumblr отображает свои посты в обратном хронологическом порядке — то есть сначала самые последние посты. Это имеет смысл для многих сайтов на Tumblr, но в этом случае я действительно хочу читать их в том порядке, в котором они были написаны. Текущий макет сайта означает, что для этого мне нужно прокрутить вниз до верхней части первого (первого написанного) поста, прокрутить вниз, когда я его прочитал, а затем прокрутить вверх до верхней части второго поста и прокрутить вниз через это …

Это крошечная, крошечная проблема первого мира — совершенно тривиальная и почти смущающая упоминание. Но это не значит, что я не хочу это исправлять.

К концу этого урока вы сделаете так, чтобы этот Tumblr или любой другой Tumblr по вашему выбору автоматически отображал свои посты в хронологическом порядке на странице.


У нас нет контроля над дизайном сайта BtotheF; он не предлагает нам никаких опций, поэтому мы не можем изменить порядок отображения сообщений на странице на стороне сервера.

Но как только HTML отправляется в браузер, он становится нашим. Мы можем манипулировать этим так, как хотим. И, конечно же, язык, с которым мы можем это сделать, это JavaScript.

Я дам короткую демонстрацию. Я буду использовать Chrome и рекомендую вам тоже — если вы не можете или не хотите, тогда возьмите копию Firebug .

Перейдите по адресу http://btothef.tumblr.com/ и откройте консоль JavaScript ( Инструменты> Консоль JavaScript , в Chrome). Тип:

1
alert(«Hi»);

… и нажмите Enter.

JavaScript Console Demo

Вот это да! … Ладно, хорошо, это мало что доказало. Попробуй это:

1
document.body.appendChild(document.createTextNode(«Hi»));

Это возьмет элемент <body> документа HTML и добавит новый текстовый узел — фрагмент текста, в основном — содержащий слово «Привет». Если вы не выполняли ничего из этого, освоитесь с HTML .

Чтобы увидеть результат, прокрутите вправо до нижней части Tumblr:

JavaScript Console Demo

Чуть более впечатляюще, поскольку мы фактически изменили содержимое страницы. Новый текстовый узел находится прямо внизу, потому что мы использовали appendChild() — который добавляет указанный узел после всего остального внутри <body> . Что касается нашего браузера, последние несколько строк HTML теперь выглядят так:

1
2
3
4
5
<!— END TUMBLR CODE —>
 
Hi
</body>
</html>

Так! Теперь у нас есть две проблемы:

  1. Как мы используем JavaScript для реорганизации всех постов на странице?
  2. Как мы можем сделать это автоматически, без необходимости возиться с консолью каждый раз, когда мы читаем Tumblr?

Мы рассмотрим их по порядку.


Сначала давайте разберемся, как «найти» все посты в HTML. В Chrome щелкните правой кнопкой мыши где-нибудь в верхнем сообщении и выберите «Проверить элемент». (Firebug должен добавить тот же ярлык в Firefox; для других браузеров, попробуйте значок увеличительного стекла.)

Осмотреть элемент (хром)

Появится вкладка «Элементы» в инструментах разработчика, в которой выделен выбранный элемент:

Вкладка Элементы

При наведении указателя мыши на различные элементы в этом представлении HTML они будут выделены на самой странице. В конце концов, вы обнаружите, что весь пост содержится в div с классом post . Как вы можете видеть на скриншоте выше, есть несколько других таких элементов div , и все они содержатся в главном div с идентификатором contents .

Мы можем выбрать все эти элементы сообщения одновременно с помощью одной строки кода; введите это в консоль JavaScript:

1
document.getElementById(«contents»).getElementsByClassName(«post»);

(Технически, мы могли бы просто написать document.getElementsByClassName("post") , но кого это волнует?)

Вы увидите — опять же, в Chrome — некоторые отзывы:

Все посты

Большой! Это указывает на то, что это сработало. Если вы нажмете на стрелки, вы увидите содержимое каждого div .

Мы могли бы также присвоить это переменной, чтобы избавить нас от необходимости вводить ее снова и снова:

1
var posts = document.getElementById(«contents»).getElementsByClassName(«post»);

(На этот раз консоль вернет undefined ; вы можете проверить, правильно ли она присвоила переменную, просто набрав слово posts в консоли.)

Мы можем перебрать все элементы в posts как если бы это был массив:

1
2
3
for (var i=0; i < posts.length; i++) {
     console.log(posts[i]);
}

Нажмите Shift-Enter, чтобы добавить новую строку в код без запуска в консоли JavaScript. console.log() просто отслеживает его содержимое до консоли, что гораздо менее раздражает, чем alert() и гораздо менее разрушительно, чем добавление текста в HTML DOM.

Результатом этого простого цикла будет просто список всех элементов div . Здорово. По-видимому, мы можем сделать что-то вроде этого …

1
2
3
4
var numPosts = posts.length;
for (var i=numPosts — 1; i >= 0; i—) {
     posts.push(posts[i]);
}

Попробуйте! Вы увидите это:

1
TypeError: Object #<NodeList> has no method ‘push’

Крысы.

Когда мы используем getElementsByClassName() , мы не получаем простой массив элементов, мы получаем NodeList . Как и массив, это коллекция элементов; в отличие от массива, коллекция обновляется в режиме реального времени. Если HTML DOM изменяется, содержимое posts изменяется в соответствии.

Например, давайте создадим новый div с классом post и добавим его к contents div:

1
2
3
var newPostDiv = document.createElement(«div»);
newPostDiv.setAttribute(«class», «post»);
document.getElementById(«contents»).appendChild(newPostDiv);

Запустите это, затем введите posts и нажмите Enter. Вы увидите дополнительный div в списке — хотя мы не трогали переменную posts .

Поскольку NodeList не является массивом, он не имеет большинства методов и свойств Array , таких как push() На самом деле мы уже использовали единственное свойство: length . Единственный метод — .item(n) , который просто получает n й элемент в списке.

Однако наша более ранняя демонстрация с appendChild() действительно предлагает альтернативное решение: что, если вместо того, чтобы пытаться переставить элементы NodeList внутри NodeList , мы переставили их на странице?

Сначала мы рассмотрим список в обратном порядке и добавим каждый элемент по очереди к contents div; Затем мы удалим оригинальные сообщения.

Обновите страницу Tumblr, чтобы убрать весь лишний мусор, который мы добавили в DOM, затем выполните первый шаг, добавив элементы в обратном порядке:

1
2
3
4
5
6
//have to define posts again, since we lost it when we refreshed
var posts = document.getElementById(«contents»).getElementsByClassName(«post»);
var numPosts = posts.length;
for (var i=numPosts — 1; i >= 0; i—) {
     document.getElementById(«contents»).appendChild(posts[i]);
}

Хорошо, теперь мы … подожди, что?

описание изображения

Первый (хронологический) пост уже находится вверху, а последний пост внизу, повторений нет. Это может показаться удивительным, но это имеет смысл: мы не клонировали какие-либо элементы NodeList , мы просто NodeList их в div contents в обратном порядке. Поскольку NodeList всегда отражает содержимое HTML DOM, мы просто переместили каждый из 11 элементов на новую позицию — 11-й элемент был добавлен в конец (без изменения позиции); затем 10-й был перемещен, чтобы быть после этого; 9-й после этого и так далее, пока 1-й элемент не оказался в самом конце.

Итак, проблема решена в пяти простых строках кода. Теперь давайте автоматизируем это.


Три из четырех лучших браузеров позволяют создавать расширения браузера — устанавливаемые дополнения — с использованием JavaScript. (Вы не удивитесь, узнав, что Internet Explorer является исключением.) Здесь приведены инструкции для Chrome , инструкции для Firefox и инструкции для Safari .

Но мы не собираемся использовать ни один из них. Вместо этого мы будем использовать отличное дополнение Greasemonkey, которое позволяет вам устанавливать маленькие JS-скрипты без необходимости упаковывать их в полноценные расширения браузера. По сути, он вставляет фрагменты JavaScript на страницу, как только она загружена.

В Chrome уже встроена поддержка Greasemonkey. В Opera есть нечто очень похожее на Greasemonkey: UserJS . Для Firefox вам нужно установить расширение Greasemonkey . Для Safari установите SIMBL , а затем GreaseKit . Для IE установите IE7Pro или Trixie .

Создайте новый текстовый файл и назовите его ChronologicalTumblr.user.js . Убедитесь, что вы удалили расширение .txt по умолчанию и используете .user.js , а не просто .js — это расширение для скрипта Greasemonkey.

Скопируйте наш код перестановки в файл:

1
2
3
4
5
var posts = document.getElementById(«contents»).getElementsByClassName(«post»);
var numPosts = posts.length;
for (var i=numPosts — 1; i >= 0; i—) {
     document.getElementById(«contents»).appendChild(posts[i]);
}

Теперь нам нужно добавить дополнительные метаданные вверху скрипта — подробности о самом скрипте. Все эти детали должны идти в комментариях, начиная с ==UserScript== и заканчивая ==/UserScript== :

1
2
3
4
5
6
7
8
9
// ==UserScript==
//
// ==/UserScript==
 
var posts = document.getElementById(«contents»).getElementsByClassName(«post»);
var numPosts = posts.length;
for (var i=numPosts — 1; i >= 0; i—) {
     document.getElementById(«contents»).appendChild(posts[i]);
}

Сначала мы добавим имя и описание:

01
02
03
04
05
06
07
08
09
10
// ==UserScript==
// @name Chronological Tumblr
// @description Displays Tumblr posts in chronological order
// ==/UserScript==
 
var posts = document.getElementById(«contents»).getElementsByClassName(«post»);
var numPosts = posts.length;
for (var i=numPosts — 1; i >= 0; i—) {
     document.getElementById(«contents»).appendChild(posts[i]);
}

Затем мы определим пространство имен . Сочетание имени и пространства имен — это то, что уникально идентифицирует скрипт; если вы попытаетесь установить другой сценарий Greasemonkey с тем же именем и пространством имен, что и у существующего сценария, он перезапишет старый — если совпадут только имя или пространство имен, он просто установит новый вместе со старым.

Обычно здесь используют URL, поэтому я буду использовать Activetuts +. Вы должны использовать свой собственный, так как вы контролируете это:

01
02
03
04
05
06
07
08
09
10
11
// ==UserScript==
// @name Chronological Tumblr
// @description Displays Tumblr posts in chronological order
// @namespace http://active.tutsplus.com/
// ==/UserScript==
 
var posts = document.getElementById(«contents»).getElementsByClassName(«post»);
var numPosts = posts.length;
for (var i=numPosts — 1; i >= 0; i—) {
     document.getElementById(«contents»).appendChild(posts[i]);
}

Теперь нам нужно указать, на какие URL должен влиять этот скрипт, то есть на какие веб-страницы должен автоматически вставляться JavaScript. Я хочу, чтобы этот скрипт работал на домашней странице BtotheF, а также на других страницах архива, например, http://btothef.tumblr.com/page/2 :

01
02
03
04
05
06
07
08
09
10
11
12
13
// ==UserScript==
// @name Chronological Tumblr
// @description Displays Tumblr posts in chronological order
// @namespace http://active.tutsplus.com/
// @include http://btothef.tumblr.com/
// @include http://btothef.tumblr.com/page/*
// ==/UserScript==
 
var posts = document.getElementById(«contents»).getElementsByClassName(«post»);
var numPosts = posts.length;
for (var i=numPosts — 1; i >= 0; i—) {
     document.getElementById(«contents»).appendChild(posts[i]);
}

* Является подстановочным знаком; это гарантирует, что скрипт будет вставлен в каждую страницу архива BtotheF Tumblr.

Мы могли бы использовать подстановочный знак, чтобы все страницы архива Тумблера отображались в хронологическом порядке:

01
02
03
04
05
06
07
08
09
10
11
12
13
// ==UserScript==
// @name Chronological Tumblr
// @description Displays Tumblr posts in chronological order
// @namespace http://active.tutsplus.com/
// @include http://*.tumblr.com/
// @include http://*.tumblr.com/page/*
// ==/UserScript==
 
var posts = document.getElementById(«contents»).getElementsByClassName(«post»);
var numPosts = posts.length;
for (var i=numPosts — 1; i >= 0; i—) {
     document.getElementById(«contents»).appendChild(posts[i]);
}

В этом случае, возможно, было бы неплохо улучшить ваш скрипт, чтобы вставить кнопку или ссылку на страницу, которая переставляет посты при нажатии. Таким образом, вы можете использовать его при необходимости, без необходимости вручную добавлять каждый URL-адрес Tumblr в качестве параметра @include .

Есть другие параметры метаданных, которые вы можете добавить, но это все, что нам нужно на данный момент.

Установить скрипт в Chrome очень просто: просто перетащите его с жесткого диска в окно браузера. Он предупредит вас о том, что сценарии могут быть опасны (просто нажмите «Продолжить»), а затем еще раз проверьте, хотите ли вы установить этот конкретный файл:

Вы уверены?

Нажмите Установить. Если вы используете другой браузер, обратитесь к соответствующему руководству скрипта Greasemonkey — в любом случае это будет просто.

После установки вы можете увидеть его в своем списке расширений:

Как и любое другое расширение.

Вернитесь на сайт http://btothef.tumblr.com/ или нажмите «Обновить», если он все еще открыт.

описание изображения

Сообщения теперь автоматически отображаются в хронологическом порядке!

Это не идеально: вы могли заметить, что кнопки навигации были перемещены в верхнюю часть страницы, поскольку они содержатся в элементе div с классом post . Но эй — если это вас не устраивает, у вас есть инструменты, чтобы исправить это сейчас.