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

Я считаю справедливым частично обвинить сегодняшние браузеры в том, что они предлагают довольно плохой и древний UX. Строго говоря, с точки зрения UI / UX, я почти не вижу инноваций, кроме как сделать браузер красивее.

Например, если вы хотите посетить ранее посещенный URL-адрес, поиск в адресной строке ужасно справляется с поиском URL-адреса, когда я начинаю вводить ключевые слова, которые я частично помню. В Chrome вроде бы происходит какой-то нечеткий поиск, но это не совсем то, что я ожидал. Я бы предпочел что-нибудь вроде замечательной команды fzf.

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

Теперь я слышу, как вы говорите мне использовать для этого расширение Chrome; но называйте меня параноиком, я не использую никаких расширений, требующих каких-либо из следующих разрешений:

Это досадно показывает, что я не использую никаких расширений Chrome, какими бы полезными они ни были. Хорошо, некоторые из них имеют открытый исходный код, но открытый исходный код не означает безопасность, когда вам лень проверять код.

Открытый исходный код не означает безопасность, если вам лень проверять код.

Так что я решил решить проблему с вкладками сам, так как никогда не лениваюсь на сделай сам.

Выбор подходящего инструмента для работы

Первое, что мне нужно было сделать, это найти способ управлять Chrome. Вот несколько альтернатив, которые я рассмотрел:

  • Написание расширения Chrome. Я не хотел писать расширение Chrome, потому что расширения полезны только тогда, когда браузер настроен. Это плохо, потому что, когда я сосредоточен на другом окне, таком как мой терминал, и хочу перейти на определенную вкладку, мне нужно будет открыть Chrome, а затем начать использовать свое расширение. Слишком много работы.
  • Использование AppleScript: AppleScript - это язык, используемый для управления приложениями с помощью Apple Events. Сначала это выглядело многообещающе, пока я не поигрался с некоторыми примерами. Используя такие языки, как C, C ++, Python, Java, Go и JavaScript, за всю свою карьеру я сразу почувствовал себя вне зоны комфорта и решил вернуться к этому, если не смогу найти лучшей альтернативы.
  • Использование Python. Я был очень взволнован, узнав, что есть некоторые библиотеки, которые можно использовать для общения с MacOS. В итоге они сильно устарели без надлежащей документации. Так что, к сожалению, для меня это была непростая ситуация, потому что я хотел двигаться быстро.

Не сдаваясь, я провел еще несколько исследований и наткнулся на JXA.

Что такое JXA?

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

Я до сих пор не знаю, что означает Х. Может быть, MacOS X? ¯ \ _ (ツ) _ / ¯

Я взглянул на веселые примечания к выпуску (написанные Apple как документация JXA…), а затем подумал: Эй, JavaScript, насколько сложно применить метод грубой силы и открыть для себя все самостоятельно?

О боже, я понятия не имел, что будет дальше.

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

Но, в конце концов, все усилия стоили того.

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

В большинстве статей говорится о написании JXA в приложении редактора скриптов или приложении Automator, которое поставляется с MacOS. Попробовав их немного и чуть не выбросив ноутбук в окно, я решил использовать редактор, который использую для всего: VSCode.

После того, как я был опытным пользователем Vim в течение ~ 4 лет, используя JetBrains IDE, Atom и Sublime в течение года, я остановился на VSCode, а после 4 лет ежедневного использования в основном с Go, а иногда и с Python, JavaScript и Markdown, я до сих пор считаю, что это лучший редактор; и каждый должен попробовать это хотя бы месяц или два, прежде чем судить.

Вот что вам нужно знать, чтобы быстро приступить к работе:

  • Создайте файл с обычным расширением js. (например, script.js)
  • Вставьте эту строку как первую строку в свой файл:
    #!/usr/bin/env osascript -l JavaScript
  • Сделайте свой скрипт исполняемым:
    chmod +x ./script.js
  • И просто запустите его с помощью:
    ./script.js

Вот пример, который вы можете попробовать:

console.log("Hello multiverse 👽")

Подключение к Chrome

Самая большая проблема, с которой я столкнулся, заключалась в том, чтобы понять, как подключаться к приложениям. Для методов нет автозаполнения, и попытка распечатать поля и методы объекта не сработала.

Затем я случайно наткнулся на статью, в которой упоминалось об использовании функции в редакторе скриптов: Открыть словарь.

И вуаля! Это было именно то, что я искал; небольшой буклет для каждого приложения, в котором перечислены методы и объекты; и Хром там был!

Вот как выглядит интерфейс.

Это не лучший вариант, но я все же могу с этим поработать.

Итак, первое, что мне нужно было сделать, это получить экземпляр Chrome:

const chrome = Application('Google Chrome')
chrome.includeStandardAdditions = true

includeStandardAdditions используется для добавления стандартных методов, таких как displayDialog, к экземпляру приложения.

Затем мне нужно было придумать, как с ним взаимодействовать. Это оказалось довольно легко.
Вот как вывести список всех вкладок во всех окнах:

chrome.windows().forEach((window, winIdx) => {
    window.tabs().forEach((tab, tabIdx) => {
        console.log(tab.title(), tab.url())
    })
})

Закрывать вкладки тоже было довольно просто:

chrome.windows[winIdx].tabs[tabIdx].close()

Сосредоточиться на определенной вкладке в определенном окне было немного сложно:

chrome.windows[winIdx].visible = true
chrome.windows[winIdx].activeTabIndex = tabIdx
chrome.windows[winIdx].index = 1
chrome.activate()

Затем я хотел распечатать текст в формате JSON, чтобы передать его команде life saver jq, и тогда я понял, что console.log печатает в stderr, и не было никакой функции для печати в stdout, и я не хотите перенаправить stderr на stdout.

К счастью, я обнаружил, что могу импортировать Objective C в JXA. Тогда я решил написать свою собственную функцию печати:

ObjC.import('stdlib')
ObjC.import('Foundation')
const print = function (msg) {
    $.NSFileHandle.fileHandleWithStandardOutput.writeData(
        $.NSString.alloc.initWithString(String(msg))
            .dataUsingEncoding($.NSUTF8StringEncoding)
    )
}

Это заняло у меня время, но понимание того, как это работает, было полезным. К счастью, у меня был небольшой Objective C опыт, который помог мне в моем проекте Смотреть трейлеры фильмов на плакатах с iPad через дополненную реальность в 2011–2012 годах, когда AR еще только зарождалась на iOS, и у меня были большие мечты ...

После этого для получения ввода с терминала мне также пришлось написать свою пользовательскую функцию ввода:

const input = function (msg) {
    print(msg)
    return $.NSString.alloc.initWithDataEncoding(
        $.NSFileHandle.fileHandleWithStandardInput.availableData,
        $.NSUTF8StringEncoding
    ).js.trim()
}

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

chrome.displayDialog(msg)

Рождение нового проекта: Chrome Control

Я собрал все в рамках проекта и назвал его Chrome Control. Возможно, кто-то сможет использовать его для интеграции со своим любимым инструментом. Возможно, интеграция с vim или fzf? Вы можете найти исходный код на GitHub.

Вот как выглядит Chrome Control:

Теперь, когда у меня был способ делать все, что мне было нужно, пришло время использовать Chrome Control через лучшее приложение для повышения производительности в мультивселенной: Alfred.

Серьезно, Apple стоит подумать о приобретении Альфреда и отказе от поддержки Spotlight.

Использование Chrome Control с Альфредом

Alfred - одно из лучших приложений для повышения производительности. Это делает меня в 10 раз продуктивнее, без преувеличения. Я создал десятки рабочих процессов, которые помогают ускорить выполнение повседневных задач.

Для этого проекта моей мечтой было нажать alt + t в любом месте MacOS и начать вводить заголовок вкладки, чтобы мгновенно увидеть список вкладок, отфильтрованных нечетким поиском, затем я просто выделил нужную вкладку и сразу же нажал Enter, чтобы перейти на эту вкладку.

Во-первых, мне нужно было создать новый рабочий процесс в Альфреде.

Я заставил Chrome Control выводить список вкладок в формате JSON для включения интеграции. Это помогает, потому что, если я хотел вывести список вкладок в Alfred, мне пришлось бы использовать фильтр сценария, а фильтр сценария требует вывода JSON с некоторыми дополнительными специальными полями.

Вот пример вывода команды list:

{
  "items": [
    {
      "title": "Inbox (1) - <hidden>@gmail.com - Gmail",
      "url": "https://mail.google.com/mail/u/0/#inbox",
      "winIdx": 0,
      "tabIdx": 0,
      "arg": "0,0",
      "subtitle": "https://mail.google.com/mail/u/0/#inbox"
    },
    {
      "title": "iPhone - Apple",
      "url": "https://www.apple.com/iphone/",
      "winIdx": 0,
      "tabIdx": 1,
      "arg": "0,1",
      "subtitle": "https://www.apple.com/iphone/"
    }
  ]
}

Поля arg и subtitle необходимы для фильтра сценария. Мы поговорим об этом немного позже.

При создании фильтра скрипта открывается это окно:

Я хотел использовать ключевое слово tabs, чтобы всякий раз, когда я набираю tabs, я видел список всех вкладок во всех окнах.

Здесь происходит еще масса других вещей. Первое, на что следует обратить внимание, - это with space argument optional. Это говорит Альфреду ожидать только команду tabs или необязательный аргумент, такой как tabs apple, который может отображать все вкладки, связанные с Apple.

У Альфреда есть замечательная нестандартная функция нечеткого поиска. Чтобы включить это, я просто установил флажок Alfred filters results.

В разделе сценариев я сказал Альфреду запустить мою list команду.
И, наконец, я перетащил значок, созданный с помощью Photoshop.

Вот как выглядит результат:

Второй шаг - привязать клавишу alt + t к этой команде. В Альфреде это очень просто сделать с помощью триггера hotkey:

Установите значение alt + t:

Затем подключите его к фильтру сценариев, перетащив соединительный кабель:

Теперь, когда у меня был способ нажать горячую клавишу и отфильтровать вкладку, пришло время сосредоточиться на выбранной вкладке, когда я нажимаю клавишу ВВОД.

Фокус на выбранной вкладке

Эта часть была немного сложной, чтобы заставить ее работать. Мне нужно было сказать Альфреду, чтобы он запустил другой скрипт с результатом Script Filter.

Помните значение arg, которое я выводил в JSON? Это было похоже на 0,1. Первым значением является Window Index, а вторым значением - Tab Index. Мне нужно было передать это arg значение команде ./chrome.js focus.

К счастью, Альфред использует это значение arg, чтобы передать его как {query} переменную шаблона для дальнейших действий, которые вы подключаете к фильтру сценариев.

Это означает, что я могу подключить выходные данные фильтра сценариев к новому действию Run Script и передать значение arg команде focus.

Затем просто запускайте команду ./chrome.js focus {query} всякий раз, когда элемент выбирается из списка.

И это сработало!

Мне также нужен был способ закрыть выделенные вкладки. Для этого я мог бы использовать клавишу alt, которая в MacOS является modifier key.

Если вы не знали об этой функции, попробуйте нажать на значок Wi-Fi или значок динамика в строке меню, удерживая нажатой клавишу alt, для доступа к некоторым дополнительным функциям.

Для этого мне нужно было подключить фильтр сценариев к моей команде close.

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

Я хотел, чтобы текст Close this tab появлялся, когда я держу клавишу alt. Результат выглядит так:

Как только это было сделано, мне захотелось добавить больше функций. Именно тогда я подумал о том, чтобы что-то сделать с моей duplicate tabs проблемой.

Дедупинг открытых вкладок

Одной из основных моих проблем были дублирующиеся вкладки. Чтобы решить эту проблему, я хотел добавить команду dedup в Chrome Control.

Он просто перебирает все открытые вкладки, находит дубликаты, а затем закрывает их.

Одна небольшая проблема, я подумал, что это будет катастрофой, если я случайно закрою вкладку с несохраненной работой, такой как полузаполненная форма, несохраненный документ, наполовину написанное электронное письмо и т. Д.

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

Вот пример вывода пяти открытых мной вкладок Hacker News:

Затем я связал это с Альфредом. Но потом я понял, что теперь мне нужна подсказка внутри браузера. Поэтому я добавил флаг --ui. Если установлен этот флаг, Chrome Control будет задавать вопросы в браузере, а не в терминале.

Я просто создал keyword trigger, а затем подключился к ./chrome.js dedup. Теперь в Chrome появится диалоговое окно с вопросом, уверен ли я!

Вот почему мы полностью добавили следующую строку в начало нашего скрипта. Это позволило нам показать этот диалог.

chrome.includeStandardAdditions = true

Закрытие вкладок по ключевым словам

Еще одна функция, которую я действительно хотел, - закрывать вкладки по ключевым словам. Ключевые слова могут существовать в URL-адресе или заголовке вкладки.

Например, если бы у меня было открыто множество документов Google, я мог бы просто ввести ./chrome.js close --url docs.google. Затем Chrome Control найдет все URL-адреса, содержащие эту строку, и закроет вкладки.

Я также хотел, чтобы это работало и с tab title. Например, если бы я проводил исследование последнего iPhone, возможно, я мог бы полностью закрыть все заголовки, содержащие iPhone, такие как ./chrome.js close --title iphone.

Итак, я пошел дальше и реализовал эти две команды.

Эти команды могут принимать несколько ключевых слов, разделенных пробелом. Или, если сама фраза включает пробел, то можно заключить фразу в двойные кавычки, например "this is a phrase with spaces".

И, конечно же, я связал их с Альфредом. На этот раз требовался аргумент.

Затем добавил действие Run Script и на этот раз использовал with input as argv. Это позволило мне использовать $@, который отправляет все введенные мной ключевые слова в Chrome Control в качестве аргументов.

Вот как я закрываю все вкладки, содержащие apple или doc.

Я повторил то же самое для создания команды close title для Альфреда.

Я также добавил дополнительные горячие клавиши для запуска других команд:

  • alt + t Список всех вкладок
  • alt + d Отключенные вкладки
  • alt + c Закрыть вкладки по URL
  • alt + shift + c Закрыть вкладки по заголовку

А вот как выглядит финальный рабочий процесс:

Исходный код и управление Chrome Рабочий процесс Альфреда

Вы можете найти дополнительную документацию по Chrome Control и всему исходному коду, упомянутому здесь, на GitHub.

Если вы чувствуете, что можете использовать что-то вроде Chrome Control Workflow в своей жизни, не стесняйтесь скачать его здесь также на GitHub.

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

Заключение

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

Конечно, было бы отлично, если бы Chrome предоставлял такие функции прямо из коробки, но я не очень оптимистичен по этому поводу.

Получайте удовольствие от навигации по вкладкам!