Расширение глобального поиска 1С, или Глобальный поиск "на максималках"
Мало кто знает, что поле "Глобального поиска" в 1С можно доработать. Добавить свои варианты поиска, кнопочки в результатах и даже целые пользовательские меню.
Предисловие
В платформе 1С есть множества интересных функциональностей. Из некоторых программисты уже выжали всё возможное и наделали много полезных инструментов.
Но есть и менее “изученные” способности платформы. Некоторые из них, на мой взгляд, совершенно несправедливо обделены вниманием. Одна из таких возможностей - поле “глобального поиска” в режиме предприятия.
Да, речь пойдёт об этом небольшом поле, которое всегда ждёт нашего внимания в правом углу приложения. Хочет, чтобы мы что-то ввели. Умеет искать по данным и метаданным, пунктам меню и справке. Имеет “глобальную” горячую клавишу. Умеет выводить результаты с иконкой, форматированной строкой, дополнительными кнопками. А главное - это поле имеет переопределяемые события, используя которые можно создавать свои виды поиска и даже целые пользовательские меню.
Как это работает
Перейдём сразу к делу.
В модуле приложения есть события:
- ПриГлобальномПоиске(СтрокаПоиска, ПланГлобальногоПоиска)
- ПриВыбореРезультатаГлобальногоПоиска(ЭлементРезультатаПоиска, СтандартнаяОбработка)
- ПриВыбореДействияРезультатаГлобальногоПоиска(ЭлементРезультата, Действие)
Используя их мы можем наделить это поле своим особым функционалом и подарить ему “новую жизнь”.
Разберём их по порядку.
ПриГлобальномПоиске(СтрокаПоиска, ПланГлобальногоПоиска)
Обработчик вызывается при вводе в строку глобального поиска (после стандартной задержки). При вызове события в параметре <ПланПоиска> передается копия плана, установленного в менеджере глобального поиска. Содержимое параметра <ПланПоиска> можно модифицировать в коде обработчика.ПланПоиска>ПланПоиска>
Это основной для нас обработчик. Начало начал. В нём мы можем в зависимости от входящей строки (или каких-то других условиях) доработать специальную коллекцию “ПланПоиска”. Можем очистить её от “стандартных” поисков или же добавить наш. Для последнего достаточно просто разместить в общем модуле подобный метод:
1
2
3
4
5
Процедура НашаПроцедура(СтрокаПоиска, РезультатыПоиска, ДополнительныеПараметры) Экспорт
//Здесь заполняем РезультатыПоиска на основе входящих параметров
КонецПроцедуры
И в событии ПриГлобальномПоиске() добавить его в ПланГлобальногоПоиска
1
2
ПланГлобальногоПоиска.Добавить(<ИмяПроцедуры>, <Модуль>,
<НаСервере>, <Фоновый>, <Порядок>, <ДополнительныеПараметры>)
Замечательно, мы добавили свой метод. Что теперь? Надо в нём заполнить результаты поиска. Для этого нужно использовать метод коллекции результатов:
1
РезультатыПоиска.Добавить(<Значение>, <Представление>, <Картинка>, <ВидПоиска>, <Описание>)
Мы не будем разбирать каждый параметр каждого метода (для этого есть справка), но сконцентрируемся на основном.
Про ВидПоиска мы вспомним чуть ниже. Сейчас главное понимать, что параметр принимает элемент стандартного поиска (специальная коллекция) или же произвольную строку.
Значение - это произвольный тип. Платформа стандартно при выборе пользователем результата постарается это значение открыть. Понимает как ссылки на базу, так и навигационные и веб-ссылки. Но, гораздо важнее, что в Значение можно поместить и другие типы. Например, структуру. В которой будет содержаться какая-то важная для нас инфа. Например, имя формы, которую нужно открыть и параметры к ней. Но как нам отловить момент нажатия на этот элемент результата?
ПриВыбореРезультатаГлобальногоПоиска(ЭлементРезультатаПоиска, СтандартнаяОбработка)
Это событие возникает, когда пользователь нажимает на какой-то элемент результата поиска. Выбранный элемент попадает в событие, где мы можем его проанализировать и, выключив Стандартную обработку, сделать какое-то своё дело. Например, открыть форму.
Кто-то из вас сразу спросит. А как отличить “мой” элемент результата от чужого? Ведь не проверять каждую структуру на свои ключи, надеясь, что другой разработчик их не будет использовать. Для этого в ЭлементРезультатаПоиска и есть ВидПоиска, о котором мы выше упомянули. Мы можем использовать свою длинную специальную ключевую строку и наделять результаты поиска особым признаком. Согласитесь, это намного легче, чем анализировать входящую структуру и проверять все её свойства.
ПриВыбореДействияРезультатаГлобальногоПоиска(ЭлементРезультата, Действие)
А зачем же это событие?
Дело в том, что в ЭлементРезультатаПоиска есть ещё одно свойство, которого мы не заметили, потому что его нет в конструкторе.
Это коллекция Действия. Представляет собой коллекцию действий элемента результата глобального поиска.
В эту коллекцию мы можем добавить свои “действия”, которые уже нужно обрабатывать программно. Как это всё выглядит у пользователя мы разберем ниже, пока просто покажем синтаксис:
ЭлементРезультата.Действия.Добавить(<Значение>, <Текст>, <Картинка>);
Всё просто. Добавляем какое-то произвольное значение, указываем текст и картинку. И это в произвольное значение мы можем так же поместить какую-то структуру со своими данными. А в обсуждаемом событии сделать с ней что-то необходимое.
Примеры на демо приложении
Теперь рассмотрим примеры доработанного поиска. Для этого воспользуемся демо-приложением от компании 1С, ссылка на которое размещается обычно рядом со ссылкой на релиз платформы. Здесь есть примеры реализации разных возможностей платформы. Качество кода оставляет желать лучшего, но на это не стоит обращать внимания, ведь “демо” сделано специально без усложнений, чтобы легче было разобраться.
И для начала зайдём в “предприятие” и посмотрим как выглядит всё это в пользовательском режиме, а потом заглянем “под капот”.
Когда мы активируем поле поиска, то сразу нас встречает доработанное описание. Для его настройки есть метод ГлобальныйПоиск.УстановитьОписание()
Из описания мы поняли, что можно ввести “+” и получить возможность быстрого создания документов. Попробуем:
И при нажатии на один из пунктов открывается карточка нового документа.
Исходя из описанного в статье выше, мы можем догадаться, как всё это работает. Заглянем в событие ПриГлобальномПоиске(). Нас интересует этот кусочек кода:
1
2
3
4
5
Если СтрокаПоиска = "+" Тогда
ПланГлобальногоПоиска.Очистить();
ПланГлобальногоПоиска.Добавить("ГлобальныйПоискКомандыСоздать", "ГлобальныйПоискКлиент", Ложь);
Возврат;
КонецЕсли;
Итак, если мы введём в строку поиска символ “+”, то стандартный план будет очищен и вместо обычного платформенного поиска сработает наш метод. Посмотрим на него:
1
2
3
4
5
6
7
8
Процедура ГлобальныйПоискКомандыСоздать(СтрокаПоиска, РезультатПоиска, ДопПараметры) Экспорт
РезультатПоиска.Добавить("+Заказ", НСтр("ru = 'Заказ'"), БиблиотекаКартинок.СоздатьЭлементСписка);
РезультатПоиска.Добавить("+Приход", НСтр("ru = 'Приходная накладная'"), БиблиотекаКартинок.СоздатьЭлементСписка);
РезультатПоиска.Добавить("+Расход", НСтр("ru = 'Расходная накладная'"), БиблиотекаКартинок.СоздатьЭлементСписка);
РезультатПоиска.Добавить("+Оплата", НСтр("ru = 'Оплата'"), БиблиотекаКартинок.СоздатьЭлементСписка);
КонецПроцедуры
Здесь достаточно простые действия. Мы добавляем в коллекцию результатов поиска наши пункты. В качестве значений указываем нашу ключевую произвольную строку. Но нам же нужно ещё открыть форму документа при нажатии на элемент результата. Для этого заглянем в событие ПриВыбореРезультатаГлобальногоПоиска()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Процедура ПриВыбореРезультатаГлобальногоПоиска(ЭлементРезультатаПоиска, СтандартнаяОбработка)
СтандартнаяОбработка = Ложь;
Если <<другой код>>
ИначеЕсли ЭлементРезультатаПоиска.Значение = "+Заказ" Тогда
ОткрытьФорму("Документ.Заказ.ФормаОбъекта");
ИначеЕсли ЭлементРезультатаПоиска.Значение = "+Приход" Тогда
ОткрытьФорму("Документ.ПриходТовара.ФормаОбъекта");
ИначеЕсли ЭлементРезультатаПоиска.Значение = "+Расход" Тогда
ОткрытьФорму("Документ.РасходТовара.ФормаОбъекта");
ИначеЕсли ЭлементРезультатаПоиска.Значение = "+Оплата" Тогда
ОткрытьФорму("Документ.Оплата.ФормаОбъекта");
Иначе
СтандартнаяОбработка = Истина;
КонецЕсли;
КонецПроцедуры
Код хоть и не очень красив, но довольно прост и понятен. Если в свойстве Значение выбранного элемента результата находится наша ключевая строка, то открываем нужную форму. Больше в данном примере разбирать особо нечего, поэтому попробуем ещё одну реализованную в демо-приложении функциональность.
Введём в поле поиска часть имени контрагента:
Здесь у нас уже более интересный результат. Пользователь получает список контрагентов с задолженностями, при нажатии на элемент результата открывается карточка выбранного, а при нажатии на кнопку “Новый заказ” создаётся новый документ с заполненным реквизитом “Покупатель”. Выглядит удобно.
Теперь изучим код. Снова заглядываем в событие ПриГлобальномПоиске(). Я приведу только нужный нам код, а всё остальное вырежу:
1
2
3
4
5
6
7
8
9
10
11
12
Процедура ПриГлобальномПоиске(СтрокаПоиска, ПланГлобальногоПоиска)
ПорядокПоискаКонтрагента = 1;
МинРазмерСтрокиДляПоискаКонтрагента = 2;
Если СтрДлина(СтрокаПоиска) >= МинРазмерСтрокиДляПоискаКонтрагента Тогда
ПланГлобальногоПоиска.Добавить("ГлобальныйПоискПоискКонтрагента", "ГлобальныйПоискСервер", Истина, Истина, ПорядокПоискаКонтрагента);
КонецЕсли;
КонецПроцедуры
Здесь дополнительно к стандартному поиску добавляется ещё и поиск с использованием нашего метода. Второй и третий параметры говорят, что метод нужно вызывать на сервере с использованием отдельного фонового задания. Т.е. платформа сама сгенерирует ФЗ, выполнит указанный метод, а результаты выведет пользователю. Последний параметр же говорит, что результаты данного поиска должны выводиться в самом начале списка. Перед всеми стандартными результатами платформы.
Заглянем в вызываемый метод. Я опять же выкинул весь “функциональный” код и оставил только заполнение результата поиска:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Процедура ГлобальныйПоискПоискКонтрагента(СтрокаПоиска, РезультатПоиска, ДопПараметры) Экспорт
<<Получаем остатки и формируем в цикле результат по каждому>>
ЭлементРезультатаПоиска = Новый ЭлементРезультатаГлобальногоПоиска();
ЭлементРезультатаПоиска.Значение = Контрагент.Значение;
МассивСтрок = Новый Массив;
МассивСтрок.Добавить(Контрагент.Представление + Символы.ПС);
МассивСтрок.Добавить(НСтр("ru = 'задолженность'", "ru") + Символы.ПС);
МассивСтрок.Добавить(Новый ФорматированнаяСтрока(
Формат(Задолжность.Сумма, "ЧДЦ=2"),
Новый Шрифт(,, Истина),
ЦветаСтиля.ЦветАкцента));
ЭлементРезультатаПоиска.Представление = Новый ФорматированнаяСтрока(МассивСтрок);
ЭлементРезультатаПоиска.Картинка = БиблиотекаКартинок.Контрагент;
ЭлементРезультатаПоиска.Действия.Добавить("Заказ", НСтр("ru = 'Новый заказ'", "ru"), БиблиотекаКартинок.СоздатьЭлементСписка);
РезультатПоиска.Добавить(ЭлементРезультатаПоиска);
КонецПроцедуры
Всё достаточно просто. Заполняем в качестве значения ссылку на контрагента, в качестве представления форматированную строку с задолжностью и картинку для красоты.
Но в данном примере появился пункт “Действия”. Это как раз та кнопочка с созданием нового заказа по контрагенту. В качестве “значения” действия указывается строка “Заказ”. И для обработки нажатия используется событие ПриВыбореДействияРезультатаГлобальногоПоиска()
1
2
3
4
5
6
7
8
9
10
11
12
Процедура ПриВыбореДействияРезультатаГлобальногоПоиска(ЭлементРезультата, Действие)
Если Действие = "Заказ" Тогда
ЗначенияЗаполнения = Новый Структура("Покупатель", ЭлементРезультата.Значение);
Параметры = Новый Структура("ЗначенияЗаполнения", ЗначенияЗаполнения);
ОткрытьФорму("Документ.Заказ.ФормаОбъекта", Параметры);
КонецЕсли;
КонецПроцедуры
Всё просто. Если в качестве действия прилетел “Заказ”, то открываем форму нового документа с заполнением поля “Покупатель”.
Плюсы и минусы глобального поиска
Итак, мы рассмотрели возможность “допила” глобального поиска. Но это ещё не конец статьи, поэтому подведем промежуточные итоги.
С такой возможностью платформы мы можем:
- Добавлять свои виды поиска и даже просто быстрые действия (добавление новых документов)
- Наглядно и удобно оформлять результаты поиска при помощи форматированной строки, картинок и “действий” (кнопок)
- Мы можем для этого использовать полностью свои методы и переопределять все события
Но теперь поговорим о минусах, которые сразу видны в примерах демо-приложения:
- Глобальные события заставляют быть осторожными. В выбранном элементе результата или действии может быть значение, которое мы считаем “своим”, но оно прилетело с другого доработанного обработчика. Короткие “ключевые” имена значений использовать небезопасно и усложняет разработку.
- Большой риск спагетти-кода. Единые точки входа провоцируют разработчика на написания кода по типу “Если А Тогда А() ИначеЕсли Б Тогда Б()”. Всё это со временем будет сложнее и сложнее поддерживать.
- Для пользователя каждая добавленная функциональность превращается в необходимость запоминать какие-то “ключевые” символы, слова или действия. Т.е. всё держится на вводе символов и если запомнить “+” и ввод имени контрагента не так сложно, то чем больше таких “фич” будет в поле поиска, тем меньше пользователи будут понимать происходящее.
- Обычно такие виды поиска дополняют стандартный. Когда мы вводим имя контрагента, чтобы получить его остатки, мы запускаем ещё и кучу других встроенных в платформу поисков. Ведь платформа не знает, что именно ищет пользователь. И выдаёт всё подряд. А это становится одновременно нагрузкой и на систему и усложнение результатов. А ведь пользователь чаще всего знает, что именно хочет найти. Самый напрашивающийся выход - добавление очередного ключевого слова. Которого опять же придётся запоминать\подглядывать и потом вручную вводить.
Лично я считаю, что именно из-за таких минусов, возможность доработки глобального поиска не обрела особую популярность. Вроде потенциал есть, но вот не хватает какого-то удобства. Причём как со стороны использования, так и разработки…
Поэтому предлагаю альтернативу.
Создаем интерактивное меню
В результате экспериментов с возможностями доработки глобального поиска родилось такое расширение-“фреймворк” под названием “Расширенный глобальный поиск”.
Разработано оно на ЕДТ (со строгой типизацией), выложено на гитхаб со всеми исходниками
Расширение специально сделано в качестве платформы для удобного добавления своих видов поиска и построения интерактивного иерархического меню.
Для демонстрации сразу доступно меню с поиском по метаданным, открытым формам и их элементам
Как это выглядит:
В чём основное отличие данного подхода.
- Пользователь запоминает только один ключевой символ, который активирует меню
- Внутри какого-то пункта меню можно:
- выводить подчиненные команды
- возвращаться “назад”
- обновлять результат
- вводить строку поиска (без спецстрок меню)
- Т.е. вообще не обращать внимание на платформенную строку поиска, которая заполняется расширением автоматически с передачей команд и параметров.
- Можно регулировать доступность пунктов меню, что позволит предоставлять разным пользователям только необходимый им функционал (например, в зависимости от прав)
- При разработке можно вообще не обращать внимания на глобальные события. В качестве выполняемых действий элементов результата можно указывать:
- открытие значения
- открытие формы с передачей параметров переход на другую команду
- выполнение описания оповещения (в котром уже релизовать свою логику)
Попробуем дополнить расширение своими пунктами. Для начала накинем его в базу, отключим все галочки кроме “Активно” и перейдём в конфигуратор:
Это, на данный момент, весь состав расширения. Разработку своего меню можно вести в РГП_ГлобальныйПоискКлиентПереопределяемый и РГП_ГлобальныйПоискСерверПереопределяемый, используя экспортные методы других модулей. Главное для нас РГП_ГлобальныйПоискКлиентПереопределяемый
Свои пункты меню нужно добавлять в методе ПослеЗаполненияПараметровРасширенногоГлобальногоПоиска(ПараметрыПоиска).
Так как расширение разрабатывалось на строгой типизации ЕДТ, то все методы прокомментированы и типы параметров указаны. По комментариям можно просмотреть все ключи параметров.
Для начала получим “корневой” элемент меню. Это можно сделать получением первого элемента, но для наглядности используем наш спец. символ:
ОбщаяКоманда = ПараметрыПоиска.Команды.Получить("\");
Далее добавим свой пункт меню, в котором и разместим новые возможности.
1
2
3
ПользовательскоеМеню = РГП_ГлобальныйПоискКлиент.НоваяКомандаРасширенногоПоиска(ПараметрыПоиска, "П", ОбщаяКоманда, 0);
ПользовательскоеМеню.Представление = "Пользовательское меню";
ПользовательскоеМеню.Картинка = БиблиотекаКартинок.Найти;
В этом пункте меню будут лежать другие виды поиска, поэтому в нём самом можно отключить отображение команды ввода строки:
1
2
3
4
ПользовательскоеМеню.ВозможнаСтрокаПоиска = Ложь;
СтандартныеКоманды = РГП_ГлобальныйПоискКлиентСервер.СтандартныеКоманды();
РГП_ГлобальныйПоискКлиент.ИсключитьСтандартнуюКомандуИзКомандыПоиска(ПользовательскоеМеню, СтандартныеКоманды.ВводСтроки);
Каждая команда имеет свой “ключ” и “родителя”. Ключ может быть любой строкой. Можно использовать просто один символ, как на примере: “П”. При выборе команды, её ключ автоматически попадает в строку поиска и так расширение понимает, в каком пункте меню мы сейчас находимся.
Всё, у нас уже есть свой пункт меню и он уже появился в интерфейсе:
Теперь нам нужно создать свои виды поиска. Возьмем для этого примеры демо-приложения, которые уже рассматривали выше. И просто перенесем их в наше расширение.
Так мы добавляем пункт меню по созданию документов и пункт по поиску контрагента:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//Создание документов
ЭлементПланаПоиска = РГП_ГлобальныйПоискКлиент.ЭлементПланаГлобальногоПоискаКоманды(
"ГлобальныйПоискСозданиеОбъектов", "РГП_ГлобальныйПоискКлиентПереопределяемый", Ложь, Ложь);
КомандаПоиска = РГП_ГлобальныйПоискКлиент.НоваяКомандаРасширенногоПоиска(
ПараметрыПоиска, "+", ПользовательскоеМеню, 0);
КомандаПоиска.Представление = "Создание документов";
КомандаПоиска.Картинка = БиблиотекаКартинок.СоздатьЭлементСписка;
КомандаПоиска.ПланПоиска.Добавить(ЭлементПланаПоиска);
КомандаПоиска.ВозможнаСтрокаПоиска = Ложь;
//Поиск контрагента
ЭлементПланаПоиска = РГП_ГлобальныйПоискКлиент.ЭлементПланаГлобальногоПоискаКоманды(
"ГлобальныйПоискПоискКонтрагента", "РГП_ГлобальныйПоискСерверПереопределяемый", Истина, Истина);
КомандаПоиска = РГП_ГлобальныйПоискКлиент.НоваяКомандаРасширенногоПоиска(
ПараметрыПоиска, "Контрагенты", ПользовательскоеМеню, 0);
КомандаПоиска.Представление = "Поиск контрагентов";
КомандаПоиска.Картинка = БиблиотекаКартинок.Найти;
КомандаПоиска.ПланПоиска.Добавить(ЭлементПланаПоиска);
Выглядит меню теперь так:
Теперь нужно взять методы из демо-приложения и доработать под наш “фреймворк”
Так мы можем сделать меню создания документов (для наглядности я сначала сформировал список, а потом заполнил результаты):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Процедура ГлобальныйПоискСозданиеОбъектов(СтрокаПоиска, РезультатыПоиска, ПараметрыВыполнения) Экспорт
Картинка = БиблиотекаКартинок.СоздатьЭлементСписка;
ДобавляемыеДокументы = Новый СписокЗначений;
ДобавляемыеДокументы.Добавить("Заказ", "Заказ");
ДобавляемыеДокументы.Добавить("ПриходТовара", "Приходная накладная");
ДобавляемыеДокументы.Добавить("РасходТовара", "Расходная накладная");
ДобавляемыеДокументы.Добавить("Оплата", "Оплата");
Для каждого СтрокаСписка Из ДобавляемыеДокументы Цикл
РГП_ГлобальныйПоискКлиентСервер.ДобавитьЭлементРезультатаГлобальногоПоиска(
РезультатыПоиска, "e1cib/data/Документ." + СтрокаСписка.Значение,
СтрокаСписка.Представление, Картинка);
КонецЦикла;
КонецПроцедуры
При нажатии на элемент результата сработает переход по навигационной ссылке на создание нового объекта. В данном случае у нас выполняется действие открытия значения. И больше ничего перехватывать не нужно. Это готовый функционал.
По можно вместо открытия пустого объекта делать открытие формы. Это мы реализуем во втором нашем виде поиска. Здесь я убрал весь код, который нам сейчас не интересен и оставил только изменения:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Процедура ГлобальныйПоискПоискКонтрагента(СтрокаПоиска, РезультатПоиска, ДопПараметры) Экспорт
<<Получаем остатки и формируем в цикле результат по каждому>>
ЭлементРезультатаПоиска = РГП_ГлобальныйПоискКлиентСервер.ЭлементРезультатаГлобальногоПоиска();
<<Заполняем элемент результата как раньше>>
ЗначенияЗаполнения = Новый Структура("Покупатель", Контрагент.Значение);
ПараметрыОткрытия = Новый Структура("ЗначенияЗаполнения", ЗначенияЗаполнения);
ВыполняемоеДействие = РГП_ГлобальныйПоискКлиентСервер.ВыполняемоеДействиеОткрытияФормы(
"Документ.Заказ.ФормаОбъекта", ПараметрыОткрытия);
ЭлементРезультатаПоиска.Действия.Добавить(ВыполняемоеДействие, НСтр("ru = 'Новый заказ'", "ru"), БиблиотекаКартинок.СоздатьЭлементСписка);
РезультатПоиска.Добавить(ЭлементРезультатаПоиска);
КонецПроцедуры
Теперь вместо передачи какой-то ключевой строки и дальнейшей её обработки мы говорим расширению “Открой форму с такими-то параметрами”. И больше ничего обрабатывать нам не нужно.
Выглядит это так:
Если же необходимое действие не ограничивается открытие значений и форм, то всегда можно использовать описание оповещения. И тогда при нажатии на элемент результата или же одну из его “кнопок”, сработает ваш метод. И больше ничего. Не нужно дополнительно отлавливать нажатия в событиях и пытаться отличить “наши” элементы от “чужих”. Пусть этим всем занимается само расширение.
Для удобства самого процесса поиска во входящих параметрах элемента плана глобального поиска расширение передаёт набор параметров, где “вычленяет” искомую строку поиска, отделяя её от ключей команды. Дополнительно есть массив со всеми “словами” строки поиска и прочие свойства, которые могут быть полезны. Более подробно можно посмотреть на самой странице расширения, и в обзорном видео
Выводы ^
В статье мы рассмотрели возможности создания своих видов поиска и доработку логики поведения строки глобального поиска.
Как “стандартно”, так и через специальное расширение, которое строит иерархическое пользовательское меню.
Надеюсь, что данную возможность платформы будут использовать чаще как разработчики так и сами пользователи, ведь потенциально это очень удобный механизм для “быстрых действий”.
Скачивайте само расширение, попробуйте его возможности. Если у вас есть идеи для доработки, то добавляйте предложение.