Создание моего первого расширения для Chrome — Reddit Filter

О, привет.

Добро пожаловать в мой первый блог. На прошлой неделе я научился создавать свое первое расширение для хрома, чтобы решить возникшую проблему, и я решил, что написание блога с документальным описанием процесса будет для меня хорошим способом напомнить себе, как работает моя программа, на случай, если мне понадобится создать что-то подобное в будущем. Если вам интересно узнать, как создать расширение, просто для развлечения или для решения проблемы, я надеюсь, что этот блог даст вам лучшее представление о том, как это сделать.

Если кратко описать мое расширение, то это пост-фильтр, который удаляет все посты Reddit, содержащие ключевые слова, указанные пользователем. Не стесняйтесь скачать его и опробовать здесь. (обязательно ознакомьтесь с инструкцией по установке).

Вот демонстрация расширения в действии.

Если вы просто хотите увидеть мой процесс создания расширения, вы можете перейти к части «Начало работы». В противном случае, не стесняйтесь читать весь блог, и я надеюсь, что он вам понравится.

Почему я создал это расширение?

С тех пор как я начал изучать полнофункциональную веб-разработку на #100Devs в январе, многие люди, с которыми я общался, говорили мне, что лучший способ применить полученные навыки — это создавать проекты. Хотя я создал несколько веб-сайтов и веб-приложений, мне всегда хотелось создать что-то более значимое для меня. Но каждая моя идея проекта была либо слишком грандиозной, либо непрактичной.

Пообщавшись с Ником Деджсусом, я узнал, что поиск вещей, которые раздражают меня ежедневно, больших или маленьких, может стать отличным способом поиска идей для проектов. Точно так же, как он создал сайт корзины, потому что заметил отсутствие поддержки библиотек корзин для веб-разработчиков.

Поэтому я начал с того, что обратил внимание на свою повседневную жизнь и поискал проблемы, которые я мог бы решить с помощью кодинга. Я быстро нашел одну: как и многие люди, пользующиеся Reddit, я иногда устаю видеть одну и ту же тему снова и снова. После нескольких часов поиска я не смог найти надежного решения для фильтрации сообщений по ключевым словам, поэтому я решил, что именно эту проблему я и попытаюсь решить.

Как много я знал

Прежде чем я написал код для этого расширения, я был знаком с HTML, CSS и JavaScript, и я создал много проектов с использованием этих инструментов. Хотя мне было удобно выполнять манипуляции с DOM на JavaScript, я не знал ничего о создании расширения для браузера.

Начало работы

Прежде чем писать код для самого расширения, я попытался написать скрипт в консоли для удаления всех сообщений Reddit, которые соответствуют выбранному мной ключевому слову. Для этого я сначала проверил элементы на случайной странице Reddit, как показано здесь: Заметили, что у выбранного мной div много братьев и сестер, и все они находятся под родительским div со странным именем класса «rpBJOHq2PR60pnwJlUyP0»? Родительский div со странным именем класса — это, по сути, контейнер, в котором хранятся все посты на странице, а каждый из прямых дочерних div (как тот, что я выбрал на скриншоте) — это отдельный пост. Другими словами, если я удалю любой прямой дочерний div под «rpBJOHq2PR60pnwJlUyP0», я удалю пост!

Чтобы выбрать любой пост, содержащий фильтрующее слово по моему выбору, я должен был посмотреть, какой элемент содержит заголовок и теги:
В данном случае элементы h3 используются для заголовков постов, а элементы span — для тегов постов. Зная это, я настроил селекторы постов следующим образом:

// class name for the post container
let postClassName = '.rpBJOHq2PR60pnwJlUyP0';

// parent container
let list = document.querySelector(`${postClassName}`);
// post titles
let titles = list.getElementsByTagName('h3');
// post tags
let tags = list.getElementsByTagName('span');
Войти в полноэкранный режим Выйти из полноэкранного режима

Я сохранил это странное имя класса в отдельной переменной «postClassName» потому, что многие имена классов на Reddit генерируются случайным образом. Поэтому на случай, если это имя как-то изменится в будущем, мне нужно будет обновить его только один раз в коде.

Теперь, поскольку переменная «list» содержит все посты Reddit в любой момент времени, и «titles», и «tags» также будут содержать все заголовки и теги на странице в любой момент времени. Поэтому мне нужно было отсортировать все заголовки и теги, и если какой-либо из них соответствовал моему ключевому слову, я выбирал его родительский элемент чуть ниже «list» и удалял его. Другими словами, я удалял посты, содержащие заголовок или тег, соответствующий моему ключевому слову. Наконец, это консольный скрипт, который я придумал:

// choose any filter word you want
let filter = 'any word';

// class name for the post container
let postClassName = '.rpBJOHq2PR60pnwJlUyP0';

// selectors
let list = document.querySelector(`${postClassName}`);
let titles = list.getElementsByTagName('h3');
let tags = list.getElementsByTagName('span');

// sort through all the titles
Array.from(titles).forEach(post => {
    if (post.textContent.toLocaleLowerCase().includes(filter)) {
        const parent = post.closest(`${postClassName} > div`);
        parent.parentNode.removeChild(parent);
    }
});

// sort through all the tags
Array.from(tags).forEach(post => {
    if (post.textContent.toLocaleLowerCase().includes(filter)) {
        const parent = post.closest(`${postClassName} > div`);
        parent.parentNode.removeChild(parent);
    }
});
Войти в полноэкранный режим Выйти из полноэкранного режима

Вот пример использования скрипта для удаления всех постов с заголовками или тегами, содержащими слово «leak»:

Настройка расширения

После того как скрипт заработал, я просмотрел несколько руководств по настройке расширения. Чтобы быстро понять основы, я нарисовал эту диаграмму, чтобы показать взаимосвязь между браузером и страницей с контентом.
PS: Основной учебник, который я использовал, был взят из Coding Train.
В двух словах, если мы хотим изменить что-то на текущей странице, мы будем изменять «содержимое» (оранжевый пузырь). Поэтому мы можем создать файл сценария с именем «content.js», и сценарий, который я показал ранее, будет помещен сюда. Но если мы хотим, чтобы пользователь что-то ввел, нам понадобится всплывающее окно для браузера. Это всплывающее окно (зеленый пузырек) представляет собой крошечную HTML-страницу, и эта HTML-страница будет иметь свой собственный файл JavaScript. Эти файлы можно назвать «popup.html» и «popup.js», соответственно. Вы, наверное, заметили, что есть еще файл с именем «manifest.json». Это самый важный файл, поскольку он сообщает браузеру о вашем расширении (т.е. имена, версии и т.д.) и о том, какие файлы используются. Кроме того, он также сообщает браузеру, какие разрешения необходимы для запуска расширения. В моем случае мне нужны были разрешения на выполнение сценариев или получение информации о вкладках, чтобы расширение знало, на какие вкладки отправлять информацию в дальнейшем. Вот код манифеста, который я использовал для своего расширения:

{
    "manifest_version": 3,
    "name": "Reddit Filter",
    "version": "1.0",
    "description": "Filter out reddit posts based on keywords",
    "content_scripts": [
        {
            "js": ["content.js"]
        }
    ],
    "action": {
        "default_popup": "popup.html"
    },
    "permissions": ["scripting", "tabs"]
}
Войти в полноэкранный режим Выйти из полноэкранного режима

Хотя первые несколько пунктов — это просто описания расширений, важно отметить, что мы должны включить manifest_version (сообщает Chrome, какую версию манифеста использовать).

Отправка сообщения

Здесь я сделал простое всплывающее окно, чтобы предоставить пользователю возможность ввести что-то: Поскольку всплывающее окно — это, по сути, HTML-файл, я могу связать с ним файл «popup.js». Задача «popup.js» — взять слово, которое пользователь набрал во всплывающем окне, и отправить его в файл «content.js».

Чтобы узнать, на какую вкладку Chrome отправить сообщение, «popup.js» должен указать вкладку или вкладки (см. переменную «params» ниже) с помощью API Chrome «chrome.tabs». Вот почему мне понадобилось разрешение «tabs», о котором говорилось ранее. Здесь я написал функцию «sendMessage», чтобы отправить текст ввода из всплывающего окна только на текущую вкладку, на которой находится пользователь:

// popup.js
// send the input keyword to the content script
function sendMessage() {
    // set parameters to select current tab only
    let params = {
        active: true,
        currentWindow: true
    }
    chrome.tabs.query(params, gotTabs);

    // send the keyword input by the user to the current tab
    function gotTabs(tab) {
        let message = {
            txt: userInput.value
        }
        chrome.tabs.sendMessage(tab[0].id, message);
    }
}
Войти в полноэкранный режим Выход из полноэкранного режима

Получение сообщения

Как только мы отправим сообщение из всплывающего окна на определенную вкладку в Chrome, нам нужно будет сообщить вкладке, чтобы она была готова принять сообщение. Поэтому в файле «content.js» мы можем использовать API Chrome «chrome.runtime» для прослушивания любого сообщения и сохранения сообщения в списке слов после его получения:

// listen for message, run gotMessage if message is received
chrome.runtime.onMessage.addListener(gotMessage);

// filter word list
let wordList = [];

function gotMessage(message) {
    let newWord = message.txt.toLowerCase();

    // put the new keyword into the word list if it's not already in there
    if (!wordList.includes(newWord)) {
        wordList.push(newWord);
    }

    // whenever we get a new keyword, we call the removePosts function once
    removePosts();
}
Вход в полноэкранный режим Выход из полноэкранного режима

Чтобы фрагмент кода не получился слишком длинным, я не стал показывать код обратного вызова «removePosts()», но не стесняйтесь заглянуть на страницу Github, чтобы узнать все подробности. По сути, это слегка измененная версия скрипта фильтра, который мы рассматривали в разделе «Начало работы».

Собираем все вместе

Как только я получил функцию фильтра и систему доставки сообщений между браузером и текущей вкладкой, у меня появилось работающее расширение фильтра Reddit.

Наконец, после того как я показал его своей одногруппнице по #100Devs Рашель, она обратила внимание на один важный момент: расширение запускает функцию фильтрации только при вводе нового слова, а не при загрузке новых сообщений, которые пользователь прокручивает вниз. Поэтому я добавил эту строку кода в самый конец файла «content.js»:

document.addEventListener('scroll', removePosts);
Войти в полноэкранный режим Выйти из полноэкранного режима

Его задача — выполнять «removePosts()» всякий раз, когда пользователь прокручивает страницу. Это также причина, по которой я сохранил вводимые пользователем слова в списке слов, а не в одной переменной, которая перезаписывается каждый раз, когда вводится новое слово. Таким образом, у нас есть работающее расширение!

Заключение

Это был веселый и увлекательный опыт — научиться создавать проект, который решает проблему, которая у меня была. Поэтому я с нетерпением жду возможности изучить новые инструменты и создать еще больше интересных вещей. В настоящее время я изучаю, как использовать React, чтобы сделать мои веб-приложения более реактивными и использовать его в своих будущих проектах. В будущем я буду писать больше постов, чтобы документировать свои проекты и прогресс в обучении.

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

Оцените статью
Procodings.ru
Добавить комментарий