Ki_rrrilll пишетКак открыть папку с закладкой по имени этой закладки или по имени папки?
Задача не столь проста, как кажется. Советую найти старое расширение "Go Parent Folder" и посмотреть, как оно работает.
Главная проблема в том, что скрипту доступны только те элементы дерева закладок, которые видны в данный момент. Если нужные закладка или папка находятся внутри свёрнутой папки, до них так просто не добраться.
Ну я все равно не разберусь как работает то расширение. И UCF я не использую.
Сейчас я открываю папку с закладкой кодом
PlacesUtils.bookmarks.fetch(guid, null, {includePath: true}) .then(res => PlacesCommandHook.showPlacesOrganizer(["AllBookmarks", ...res.path.map(b => b.guid), guid]))
А guid беру из резервной копии закладок в формате json. Ни о каких "живых" результатах конечно и речи нет, ну хоть как то.
Так вот я думал, может можно из этого json получать guid, по ключевому слову закладки? И желательно имя родительской папки.
Потому что я не очень умею обращаться с этими json и сейчас выуживаю нужный guid путем многочисленных split-ов, отсекая ненужное.
Отредактировано Ki_rrrilll (04-05-2025 00:02:27)
Отсутствует
А guid беру из резервной копии закладок в формате json.
А что является критерием для поиска этого GUID? Имя закладки, URL, метка, ключевое слово или что?
Так вот я думал, может можно из этого json получать guid, по ключевому слову закладки? И желательно имя родительской папки.
Потому что я не очень умею обращаться с этими json и сейчас выуживаю нужный guid путем многочисленных split-ов, отсекая ненужное.
Если преобразовать JSON в объект (а это просто один вызов функции JSON.parse(str)), то можно прогуляться по получившемуся дереву и найти в нём нужный элемент. А заодно в процессе этой прогулки и путь к закладке построится.
Только вот алгоритм прохода по дереву абсолютно аналогичен алгоритму, который используется скрипте SidebarBookmarkSearchOpenFolder. Зато скрипт не нуждается в JSON-е, он напрямую с данными окна работает.
И ещё мне кажется, что если в поиске информации о нужной закладке используется человек, то, наверное, имеет смысл взять ранее упомянутое расширение "Go Parent Folder" и засунуть его в кнопку. Потому что работает расширение так: ткнулись мышкой в нужную закладку, выбрали из её контекстного меню пункт "Go Parent Folder" - и происходит переход в папку, в которой где эта закладка лежит. А слева, соответственно, дерево до этой папки разворачивается и папка выбирается (становится текущей).
А SidebarBookmarkSearchOpenFolder делает то же самое, только не в окне, а в боковой панели.
И UCF я не использую.
В кнопку данный скрипт засовывается с минимальными усилиями.
Отредактировано yup (04-05-2025 02:44:26)
Отсутствует
Всем привет!
Если вы используете это расширение, отличные новости — с помощью ИИ удалось устранить баги, возникшие при работе в Firefox 138.
Теперь код для расширения снова работает корректно.
Оригинальный код был создан Dumby и отлично себя показал в более ранних версиях браузера.
Новая версия кода базируется на том же исходнике, но адаптирована под последние изменения Firefox.
Финальная версия доступна здесь:
// ==UserScript== // @name Иконки поисковых систем из расширения ContextSearch-web-ext в контекстном меню // @namespace cswem // @version 2.1 // @description Редактирует контекстное меню Firefox // @match *://*/* // @grant none // @icon https://www.mozilla.org/favicon.ico // ==/UserScript== (function() { 'use strict'; // Конфигурация const SETTINGS = { initializedFlag: 'cswemInitialized', // Флаг инициализации menuId: 'cswem-menugroup', // ID группы меню styleId: 'cswem-styles', // ID стилей hiddenItems: [ // Элементы для скрытия '_5dd73bb9-e728-4d1e-990b-c77d8e03670f_-menuitem-_root_menu', 'context-searchselect', 'context-keywordfield' ] }; // Проверяем, не запущен ли скрипт ранее if (window[SETTINGS.initializedFlag]) return; window[SETTINGS.initializedFlag] = true; try { // ██████████████████████████████████████████████████████████████ // █ 1. ДОБАВЛЯЕМ СТИЛИ ДЛЯ СКРЫТИЯ ЛИШНИХ ЭЛЕМЕНТОВ И НАСТРОЙКИ МЕНЮ // ██████████████████████████████████████████████████████████████ const style = document.createElement('style'); style.id = SETTINGS.styleId; style.textContent = ` #${SETTINGS.menuId} { padding-left: 30px; display: grid; grid-template-columns: repeat(auto-fill, 32px); grid-auto-rows: 26px; gap: 2px; } #${SETTINGS.menuId} > menuitem { -moz-box-pack: center; list-style-image: var(--image) !important; } ${SETTINGS.hiddenItems.map(id => `#${id}`).join(', ')}, #${SETTINGS.menuId}:empty { display: none !important; } `; document.head.appendChild(style); // ██████████████████████████████████████████████████████████████ // █ 2. СОЗДАЕМ КОНТЕЙНЕР ДЛЯ ИКОНОК // ██████████████████████████████████████████████████████████████ const menugroup = document.createXULElement('menugroup'); menugroup.id = SETTINGS.menuId; // Размещаем после поисковой строки или в начало меню const searchSelect = document.getElementById('context-searchselect'); if (searchSelect) { searchSelect.after(menugroup); } else { document.getElementById('contentAreaContextMenu').prepend(menugroup); } // ██████████████████████████████████████████████████████████████ // █ 3. ФУНКЦИЯ ФИЛЬТРАЦИИ: ОПРЕДЕЛЯЕМ, КАКИЕ ЭЛЕМЕНТЫ ОСТАВИТЬ // ██████████████████████████████████████████████████████████████ const shouldKeepItem = (item) => { const forbiddenClasses = ['menuitem-iconic-webext', 'extension-menu-item']; const forbiddenIds = ['ublock', 'adguard', 'grammarly', 'ext-', 'extension-', ...SETTINGS.hiddenItems]; return ( item.tagName === 'menuitem' && !item.hidden && item.getAttribute('image') && item.getAttribute('label') && !item.id?.includes('_separator') && !forbiddenClasses.some(c => item.classList.contains(c)) && !forbiddenIds.some(id => item.id?.includes(id)) ); }; // ██████████████████████████████████████████████████████████████ // █ 4. ОБНОВЛЕНИЕ МЕНЮ: УДАЛЯЕМ СТАРЫЕ ЭЛЕМЕНТЫ И ДОБАВЛЯЕМ НОВЫЕ // ██████████████████████████████████████████████████████████████ const refreshMenu = () => { // Полностью очищаем меню перед обновлением while (menugroup.firstChild) { menugroup.removeChild(menugroup.firstChild); } // Находим все элементы меню и фильтруем их const allMenuItems = Array.from(document.querySelectorAll('#contentAreaContextMenu menuitem')); const validItems = allMenuItems.filter(shouldKeepItem); // Клонируем и добавляем только нужные элементы validItems.forEach(item => { const clone = item.cloneNode(true); clone.id = ''; // Удаляем ID, чтобы избежать конфликтов // Назначаем обработчик клика clone.addEventListener('command', () => { try { item.click(); } catch (e) { console.error('Ошибка при клике:', e); } }); menugroup.appendChild(clone); }); // Скрываем группу, если нет элементов menugroup.hidden = menugroup.children.length === 0; }; // ██████████████████████████████████████████████████████████████ // █ 5. НАСТРОЙКА СОБЫТИЙ: ОБНОВЛЯЕМ МЕНЮ ПРИ КАЖДОМ ОТКРЫТИИ // ██████████████████████████████████████████████████████████████ const contextMenu = document.getElementById('contentAreaContextMenu'); contextMenu.addEventListener('popupshowing', refreshMenu); // ██████████████████████████████████████████████████████████████ // █ 6. ОЧИСТКА: УДАЛЯЕМ ЭЛЕМЕНТЫ ПРИ ЗАКРЫТИИ СТРАНИЦЫ // ██████████████████████████████████████████████████████████████ window.addEventListener('unload', () => { contextMenu.removeEventListener('popupshowing', refreshMenu); document.getElementById(SETTINGS.menuId)?.remove(); document.getElementById(SETTINGS.styleId)?.remove(); delete window[SETTINGS.initializedFlag]; }); } catch (error) { console.error('Ошибка в скрипте контекстного меню:', error); } })();
Кинуть код в поле «Инициализация».
Иконки поисковых систем автоматически подгружаются в контекстное меню из установленного дополнения ContextSearch-web-ext.
Отредактировано leex (04-05-2025 12:23:42)
Отсутствует
Ki_rrrilll пишетА guid беру из резервной копии закладок в формате json.
А что является критерием для поиска этого GUID? Имя закладки, URL, метка, ключевое слово или что?
Имя или ключевое слово. Желательно, чтоб можно было искать и так, и так. Поиск по ключевому слову нужнее, по имени закладку можно найти через встроенный поиск, а вот если помнишь только ключевое слово - то никак. Можно конечно по ключевому слову открыть закладку, увидеть ее URL и вставить его в поле поиска. Но ведь это не выход.
Если преобразовать JSON в объект (а это просто один вызов функции JSON.parse(str)), то можно прогуляться по получившемуся дереву и найти в нём нужный элемент. А заодно в процессе этой прогулки и путь к закладке построится.
Только вот алгоритм прохода по дереву абсолютно аналогичен алгоритму, который используется скрипте SidebarBookmarkSearchOpenFolder. Зато скрипт не нуждается в JSON-е, он напрямую с данными окна работает.
Я открывал свой JSON в онлайн парсере, видел там это дерево. Но я не программист и не знаю как вытащить из него то что мне нужно. А нужно мне: по ключевому слову определить имя закладки, имя папки и желательно имя родителя папки. Просто получить эту информацию, скажем, в виде алерта.
Ну и GUID, на случай если захочу увидеть закладку в ее папке.
И ещё мне кажется, что если в поиске информации о нужной закладке используется человек, то, наверное, имеет смысл взять ранее упомянутое расширение "Go Parent Folder" и засунуть его в кнопку. Потому что работает расширение так: ткнулись мышкой в нужную закладку, выбрали из её контекстного меню пункт "Go Parent Folder" - и происходит переход в папку, в которой где эта закладка лежит.
Я как раз хочу найти решение для случаев, когда ткнуть в закладку не получается - я не знаю где она лежит. Надо закладку сперва найти. Особенно это касается букмарклетов. Знаю, что есть у меня такой букмарклет, знаю keyword для его вызова. А как называется - не помню.
Отсутствует
Ki_rrrilll
В тех браузерах, которыми я пользуюсь, у закладок есть Tags / Метки и Keyword / Краткое имя. Очевидно, мы сейчас говорим о кратких именах.
Firefox в связи с особенностями интерфейса окна Библиотека показывает в таблице только Метки. Если попытаться обойтись совсем-совсем без программирования, то есть два решения:
А если с программированием, то вместо поиска по GUID вполне можно искать сразу по кратким именам:
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); let keyword = prompt("Что ищем?"); if (!keyword ) return; PlacesUtils.keywords.fetch(keyword) .then(res => { if (!res) return; let query = PlacesUtils.history.getNewQuery(); query.searchTerms = res.url.href; let options = PlacesUtils.history.getNewQueryOptions(); options.queryType = 1; // QUERY_TYPE_BOOKMARKS; res = PlacesUtils.history.executeQuery(query, options); res.root.containerOpen = true; let guid = res.root.getChild(0).bookmarkGuid; /* что-то делать с guid */ PlacesUtils.bookmarks.fetch(guid, null, {includePath: true}) .then(res => PlacesCommandHook.showPlacesOrganizer(["AllBookmarks", ...res.path.map(b => b.guid), guid])); }, error => { console.log(error); } );
Но что лучше всего делать с полученным guid, я сказать на могу. Скорее всего, его можно засунуть в:
PlacesUtils.bookmarks.fetch(guid, null, {includePath: true}) .then(res => PlacesCommandHook.showPlacesOrganizer(["AllBookmarks", ...res.path.map(b => b.guid), guid]))
- как у меня в коде, и сразу откроется нужное окно, но проверить это я не могу, потому что у доступных мне браузеров у функции PlacesUtils.bookmarks.fetch() нет параметра {includePath: true}.
Так что дальше пусть кто-то из местных владельцев современных Firefox поможет, если это не заработает.
Но обращаю внимание: если имеется несколько закладок с одинаковым URL, и одной из них прописать Keyword / Краткое имя, то то же самое окажется прописанным и у всех остальных закладок, потому что браузер эти имена присваивает не закладкам, а URL-ам.
Соответственно, если закладок с одинаковым URL-ом будет существовать несколько, то мой код покажет только первую из них.
Отредактировано yup (06-05-2025 10:26:16)
Отсутствует
Если в адресной строке набрать краткое имя, то под адресной строкой браузер вывалит найденную по этому слову закладку, после чего можно скопировать её URL и но нему найти закладку обычным поиском.
Это как раз то, о чем я и писал постом выше. Что это способ неудобный
Но что лучше всего делать с полученным guid, я сказать на могу. Скорее всего, его можно засунуть в:
Выделить кодКод:
PlacesUtils.bookmarks.fetch(guid, null, {includePath: true}) .then(res => PlacesCommandHook.showPlacesOrganizer(["AllBookmarks", ...res.path.map(b => b.guid), guid]))
Это как раз то, что я и делал до сих пор
Просто получить guid чтобы сюда вставлять для меня было затруднительно.
А вот код ваш я утащил. Большое спасибо!
Но ведь ваш код открывает менеджер закладок тоже через guid? Иначе наверное никак не открыть?
Отсутствует
Это как раз то, что я и делал до сих пор
Да. Мне пришлось этому коду "поверить на слово", потому что проверить его в своих браузерах я не могу.
Но ведь ваш код открывает менеджер закладок тоже через guid?
Точнее будет сказать: открывает с указанием guid-ов закладки, которую нужно выделить, и всех папок, которые нужно развернуть, чтобы до неё добраться. (Зачем эту цепочку папок нужно указывать, я не знаю. По идее, код браузера и сам мог бы её получить, вызвав ту же самую функцию bookmarks.fetch()).
Может быть, для указания нужной закладки можно использовать не только её guid, но и ещё что-то, но мне посмотреть негде.
Иначе наверное никак не открыть?
Закладки можно открыть разными способами. Но открыть окно, развернуть папки и выделить нужную закладку - этот способ самый простой. Альтернативные варианты здесь приводились в предыдущие два дня.
Отсутствует
yup
Вот еще добавить бы к вашему коду проверку - если окно менеджера закладок уже существует, то закрыть его, а потм уже выполнять код дальше.
А то бывает, не заметишь, что окно свернуто или просто в фоне, тогда в консоли появляется ошибка
Uncaught (in promise) NS_ERROR_ILLEGAL_VALUE: Component returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE) [nsINavHistoryContainerResultNode.getChild]
И на этом все останавливается.
Отредактировано Ki_rrrilll (07-05-2025 22:55:04)
Отсутствует
Ki_rrrilll
Надо же... Я думал, в функции showPlacesOrganizer() возможность такой ситуации учтена.
Как закрыть имеющееся окно, я знаю. Но прежде, чем это добавлять, прошу проверить вариант:
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); let keyword = prompt("Что ищем?"); if (!keyword ) return; PlacesUtils.keywords.fetch(keyword) .then(kwd => { if (!kwd) return; let query = PlacesUtils.history.getNewQuery(); query.searchTerms = kwd.url.href; let options = PlacesUtils.history.getNewQueryOptions(); options.queryType = 1; // QUERY_TYPE_BOOKMARKS; let result = PlacesUtils.history.executeQuery(query, options); result.root.containerOpen = true; let guid = result.root.getChild(0).bookmarkGuid; PlacesUtils.bookmarks.fetch(guid, null, {includePath: true}) .then(res => PlacesCommandHook.showPlacesOrganizer(["AllBookmarks", ...res.path.map(b => b.guid), guid])); }, error => { console.log(error); } );
Отсутствует
yup
Все работает, обошлось "малой кровью"
Спасибо!
Отсутствует