diff --git a/compile.sh b/compile.sh new file mode 100644 index 0000000..b4b4d4c --- /dev/null +++ b/compile.sh @@ -0,0 +1 @@ +web-ext -s firefox run -f "C:\Program Files\Firefox Developer Edition\firefox.exe" -p "C:\Users\Пользователь\AppData\Roaming\Mozilla\Firefox\Profiles\development" --profile-create-if-missing --keep-profile-changes --verbose diff --git a/firefox/_locales/en/messages.json b/firefox/_locales/en/messages.json new file mode 100644 index 0000000..3e13428 --- /dev/null +++ b/firefox/_locales/en/messages.json @@ -0,0 +1,52 @@ +{ + "extensionName": { + "message": "Menu demo", + "description": "Name of the extension." + }, + + "extensionDescription": { + "message": "Demonstrates the menus API.", + "description": "Description of the add-on." + }, + + "menuItemSelectionLogger": { + "message": "Log '%s' to the browser console", + "description": "Title of context menu item that logs the selected text when clicked." + }, + + "menuItemRemoveMe": { + "message": "Remove me!", + "description": "Title of context menu item that removes itself when clicked." + }, + + "menuItemGreenify": { + "message": "Greenify", + "description": "Title of context menu item that adds a green border when clicked." + }, + + "menuItemBluify": { + "message": "Bluify", + "description": "Title of context menu item that adds a green border when clicked." + }, + + "menuItemCheckMe": { + "message": "Check me", + "description": "Title of context menu item when the item is checked." + }, + + "menuItemUncheckMe": { + "message": "Uncheck me", + "description": "Title of context menu item when the item is unchecked." + }, + + "menuItemOpenSidebar": { + "message": "Open sidebar", + "description": "Title of context menu item that opens a sidebar." + }, + + "menuItemToolsMenu": { + "message": "Click me!", + "description": "Title of tools menu item." + } + + } diff --git a/firefox/pages/settings/index.js b/firefox/pages/settings/index.js index 95856ac..0b9b271 100644 --- a/firefox/pages/settings/index.js +++ b/firefox/pages/settings/index.js @@ -83,8 +83,10 @@ class page { this.blocks.header('Убийца'), // text('Удаление активности выбранных пользователей'), this.blocks.fields.checkbox('activate', 'lightning', 'Активировать'), - this.blocks.fields.checkbox('list', 'lightning', 'Заблокированные ВКонтакте', 'Удалять тех кто находится в списке заблокированных ВКонтакте'), + this.blocks.fields.text('asdasdasd', 'lightning', 'Активировать', 'asdasdasdasd', 'Тестирование всплывающей подсказки', 'text', 0, 8, 'фффф', 'сюда писать'), + this.blocks.fields.checkbox('list', 'group', 'Заблокированные ВКонтакте', 'Удалять тех кто находится в списке заблокированных ВКонтакте', 'Тестирование всплывающей подсказки'), this.blocks.fields.checkbox('target', 'list', 'Отдельный список на удаление', 'Выбрать пользователей вручную'), + this.blocks.fields.dropdown('targetasdasd', 'promo', 'Какаято хуитень', 'Выбфывелей вручную', 'Тестирование всплывающей подсказки'), modules.killer.list('asdasd'), ); } @@ -122,7 +124,7 @@ class page { */ header(text = '') { /** - * Запись в группу (подразумевается выполнение в функции генерирующую группу) + * Запись в группу (подразумевается выполнение в функции генерирующей группу) * * @param {string} group Группа * @@ -133,20 +135,19 @@ class page { // Пройдена проверка входных параметров // Инициализация блока куда надо записать заголовок - let block = document.getElementById('block_' + group); + const block = document.getElementById('block_' + group); // Инициализация оболочки заголовка - let title = block.getElementsByTagName('h2')[0].getElementsByClassName('page_block_header')[0]; + const title = block.getElementsByTagName('h2')[0].getElementsByClassName('page_block_header')[0]; // Запись содержимого в буфер - let buffer = title.cloneNode(true); + const buffer = title.cloneNode(true); // Запись заголовка title.innerText = text; // Запись содержимого заголовка из буфера - for (let element of buffer.children) - title.appendChild(element); + for (let element of buffer.children) title.appendChild(element); return true; } @@ -166,7 +167,7 @@ class page { */ header_extra(left = '', center = '', right = '') { /** - * Запись в группу (подразумевается выполнение в функции генерирующую группу) + * Запись в группу (подразумевается выполнение в функции генерирующей группу) * * @param {string} group Группа * @@ -175,11 +176,12 @@ class page { return function (group) { if (typeof group === 'string' && typeof left === 'string' && typeof center === 'string' && typeof right === 'string') { // Пройдена проверка входных параметров + // Инициализация блока - let block = document.getElementById('block_' + group); + const block = document.getElementById('block_' + group); // Инициализация оболочки заголовка - let title = block.getElementsByTagName('h2')[0].getElementsByClassName('page_block_header')[0]; + const title = block.getElementsByTagName('h2')[0].getElementsByClassName('page_block_header')[0]; // Запись заголовков title.getElementsByClassName('page_block_header_extra_left _header_extra_left')[0].innerText = left; @@ -294,22 +296,268 @@ class page { * Поля */ fields: { + macroses: { + /** + * Шаблон для инициализации элементов строки с полем + * + * @param {string} group Группа + * @param {string|null} icon Иконка + * @param {function} content Функция которая будет обрабатывать содержимое макроса + */ + row(group, icon, content) { + // Инициализация блока + const block = document.getElementById('block_' + group); + + // Инициализация головного элемента блока + const header = block.getElementsByClassName('page_block_header')[0]; + + // Инициализация элемента со статусом + const status = header.getElementsByClassName('page_block_saved')[0]; + + // Инициализация тела блока + const body = block.getElementsByClassName('settings_panel clear_fix settings_' + this.core.id + ' settings_section_' + this.core.id)[0]; + + // Инициализация оболочки кнопки активации + const wrap = document.createElement('div'); + wrap.classList.add('settings_separated_row', this.core.icons[icon] ?? this.core.icons['lightning'], 'settings_separated_row_iconed'); + + // Инициализация разделителя + const separator = document.createElement('div'); + separator.classList.add('settings_separated_row_extra'); + + // Генерация содержимого строки + return content(status, body, wrap, separator); + }, + + dropdown(active, rows) { + if (typeof active === 'string' && typeof rows === 'object') { + // Пройдена проверка входных параметров + + // Инициализация оболочки + const wrap = document.createElement('div'); + wrap.classList.add('privacy_dropdown', 'privacy_dropdown_mail', 'pdd_ralign'); + wrap.setAttribute('style', 'opacity: 1; display: none;'); + + // Инициализация списка строк + const list = document.createElement('div'); + list.classList.add('rows', 'rows__flex'); + wrap.setAttribute('style', 'font-size: 13px;'); + + // Инициализация головной строки + const header = document.createElement('div'); + header.classList.add('header'); + wrap.setAttribute('onclick', 'Privacy.hide(-1)'); + + // Инициализация активной строки (выбранного параметра) + const active = document.createElement('div'); + active.classList.add('header_label'); + active.innerHTML = active; + + // Инициализация основной группы строк + const body = document.createElement('div'); + body.classList.add('body', 'body__flex'); + body.setAttribute('role', 'list'); + body.setAttribute('aria-labelledby', 'privacy_who_can_view'); + + for (const row in rows) { + // Перебор строк для генерации списка + + // Инициализация кнопки + const button = document.createElement('button'); + } + } + + return null; + } + }, + /** * Генерация HTML-элемента настройки с кнопкой активации * * @param {string} id Идентификатор + * @param {string|null} icon Иконка * @param {string|null} name Название + * @param {string|null} description Описание + * @param {string|null} hint Всплывающая подсказка (иконка с вопросительным знаком возле верхнего колонтинула) + * @param {string|null} onchange Код который выполняется после изменения состояния * - * @return {Function} Функция для выполнения в генераторе группы + * @return {function} Функция для выполнения в генераторе группы * * @todo 1. Добавить проверку на существование нижнего колонтинула и записывать перед ним, вместо конца блока */ - checkbox(id, icon, name, description) { + checkbox(id, icon, name, description, hint, onchange) { // Инициализация ядра - let core = this.core; + const core = this.core; /** - * Запись в группу (подразумевается выполнение в функции генерирующую группу) + * Запись в группу (подразумевается выполнение в функции генерирующей группу) + * + * @param {string} group Группа + * + * @return {bool} Статус выполнения + */ + return function (group) { + if (typeof id === 'string' && typeof core === 'object' && typeof group === 'string') { + // Пройдена проверка входных параметров + + return core.blocks.fields.macros(group, icon, + function (status, body, wrap, separator) { + wrap.addEventListener("click", fn => { + // Инициализация кнопки + const button = wrap.getElementsByClassName('ui_toggler')[0]; + + if (button.classList.contains('on')) { + // Активирована + if (settings.write(group + '_' + id, false)) { + // Записан статус активации + + // Запуск анимации и переход в состояние деактивации + button.classList.remove('on'); + + settings.read(group + '_' + id).then(result => { + if (result === false) { + // Сохранены изменения + + // Запуск анимации + status.style.transition = '0.5s'; + status.style.opacity = 1; + setTimeout(fn => { + status.style.transition = '1.5s'; + status.style.opacity = 0; + }, 1000); + } + }); + } + } else { + // Деактивирована + if (settings.write(group + '_' + id, true)) { + // Записан статус активации + + // Запуск анимации и переход в состояние активации + button.classList.add('on'); + + settings.read(group + '_' + id).then(result => { + if (result === true) { + // Сохранены изменения + + // Запуск анимации + status.style.transition = '0.5s'; + status.style.opacity = 1; + setTimeout(fn => { + status.style.transition = '1.5s'; + status.style.opacity = 0; + }, 1000); + } + }); + } + } + }); + + // Инициализация кнопки активации + const button = document.createElement('div'); + button.classList.add('ui_toggler_wrap'); + + // Инициализация элемента-иконки + const checkbox = document.createElement('div'); + checkbox.classList.add('_ui_toggler', 'ui_toggler', '_settings_ienable'); + if (typeof onchange === 'string') checkbox.setAttribute('onchange', onchange); + + settings.read(group + '_' + id).then(result => { + // Получены данные о значении настройки + + // Запись состояния + if (result) checkbox.classList.add('on'); + }); + + // Инициализация элемента-иконки + const label = document.createElement('div'); + label.classList.add('ui_toggler_label'); + + // Инициализация названия + const header = document.createElement('div'); + header.classList.add('settings_separated_row_text'); + + // Инициализация текста названия + const colonic = document.createElement('div'); + colonic.classList.add('settings_separated_row_text_inner'); + colonic.innerText = name !== undefined && typeof name === 'string' ? name : id; + + // Инициализация архитектуры + button.appendChild(checkbox); + separator.appendChild(button); + wrap.appendChild(separator); + header.appendChild(colonic); + + if (typeof hint === 'string') { + // Получена подсказка + + // Инициализация текста-подсказки + const question = document.createElement('span'); + question.classList.add('hint_icon'); + question.setAttribute('data-title', hint); + question.setAttribute('onmouseover', 'showHint(this);'); + + // Инициализация архитектуры + colonic.appendChild(question); + } + + if (description !== undefined && typeof description === 'string') { + // Получено описание + + // Инициализация текста описания + const text = document.createElement('div'); + text.classList.add('settings_separated_row_hint'); + text.innerText = description; + + // Инициализация архитектуры + colonic.appendChild(text); + } + + // Инициализация архитектуры + wrap.appendChild(header); + + // Запись в блок + body.appendChild(wrap); + + return true; + } + ); + } + + return false; + }; + }, + + /** + * Генерация HTML-элемента настройки с полем для ввода текста + * + * @param {string} id Идентификатор + * @param {string|null} icon Иконка + * @param {string|null} name Название + * @param {string|null} description Описание + * @param {string|null} hint Всплывающая подсказка (иконка с вопросительным знаком возле верхнего колонтинула) + * @param {string|null} type Тип поля для ввода (HTML-категории: text, password, email, number) + * @param {number|null} min Ограничение по минимальному количеству символов + * @param {number|null} max Ограничение по максимальному количеству символов + * @param {string|null} title Текст выводимый во всплывающем окне при наведении курсора + * @param {string|null} placeholder Текст отображаемый в поле для ввода если оно пустое + * @param {string|null} pattern Шаблон значений которые позволено вводить в поле + * @param {boolean|null} readonly Запретить редактирование + * @param {boolean|null} spellcheck Использовать проверку орфографии + * @param {object|null} options Массив со строками для автозаполнения поля + * @param {string|null} onchange Код который выполняется после изменения состояния + * @param {string|null} oninput Код который выполняется при вводе текста + * + * @return {function} Функция для выполнения в генераторе группы + * + * @todo 1. Добавить проверку на существование нижнего колонтинула и записывать перед ним, вместо конца блока + */ + text(id, icon, name, description, hint, type = 'text', min = 0, max = 100, title, placeholder, pattern, readonly, spellcheck, options, onchange, oninput) { + // Инициализация ядра + const core = this.core; + + /** + * Запись в группу (подразумевается выполнение в функции генерирующей группу) * * @param {string} group Группа * @@ -319,37 +567,18 @@ class page { if (typeof id === 'string' && typeof icon === 'string' && typeof core === 'object' && typeof group === 'string') { // Пройдена проверка входных параметров - // Инициализация блока - const block = document.getElementById('block_' + group); + return core.blocks.fields.macros(group, icon, + function (status, body, wrap, separator) { + wrap.addEventListener("input", fn => { + // Инициализация поля ввода + const input = wrap.getElementsByTagName('input')[0]; - // Инициализация верхнего колонтинула блока - const header = block.getElementsByClassName('page_block_header')[0]; - - // Инициализация элемента со статусом - const status = header.getElementsByClassName('page_block_saved')[0]; - - // Инициализация тела блока - const body = block.getElementsByClassName('settings_panel clear_fix settings_' + core.id + ' settings_section_' + core.id)[0]; - - settings.read(id).then(result => { - // Инициализация оболочки кнопки активации - let wrap = document.createElement('div'); - wrap.classList.add('settings_separated_row', core.icons[icon] ?? core.icons['lightning'], 'settings_separated_row_iconed'); - - wrap.addEventListener("click", fn => { - // Инициализация кнопки - let button = wrap.getElementsByClassName('ui_toggler')[0]; - - if (button.classList.contains('on')) { - // Активирована - if (settings.write(group + '_' + id, false)) { - // Записан статус активации - - // Запуск анимации и переход в состояние деактивации - button.classList.remove('on'); + if (settings.write(group + '_' + id, input.value)) { + // Записано значение поля + // Запуск анимации settings.read(group + '_' + id).then(result => { - if (result === false) { + if (result === input.value) { // Сохранены изменения // Запуск анимации @@ -362,91 +591,255 @@ class page { } }); } - } else { - // Деактивирована - if (settings.write(group + '_' + id, true)) { - // Записан статус активации + }); - // Запуск анимации и переход в состояние активации - button.classList.add('on'); + // Инициализация элемента для ввода текста + const input = document.createElement('input'); + input.classList.add('dark'); + input.setAttribute('type', type); + input.setAttribute(type === 'number' ? 'min' : 'minlength', min); + input.setAttribute(type === 'number' ? 'max' : 'maxlength', max); + if (typeof title === 'string') input.setAttribute('title', title); + if (typeof placeholder === 'string') input.setAttribute('placeholder', placeholder); + if (typeof pattern === 'string') input.setAttribute('pattern', pattern); + if (readonly === true) input.setAttribute('readonly', readonly); + if (spellcheck === true) input.setAttribute('spellcheck', spellcheck); + input.setAttribute('list', group + '_' + id + '_datalist'); + settings.read(group + '_' + id).then(result => { + // Получены данные о значении настройки - settings.read(group + '_' + id).then(result => { - if (result === true) { - // Сохранены изменения + // Запись значения в поле + if (result) input.value = result; + }); + if (typeof onchange === 'string') input.setAttribute('onchange', onchange); + if (typeof oninput === 'string') input.setAttribute('oninput', oninput); - // Запуск анимации - status.style.transition = '0.5s'; - status.style.opacity = 1; - setTimeout(fn => { - status.style.transition = '1.5s'; - status.style.opacity = 0; - }, 1000); - } - }); + + if (typeof options === 'object' && options.length > 0) { + // Инициализация элемента-списка для выбора значений автозаполнения + const datalist = document.createElement('datalist'); + datalist.id = group + '_' + id + '_datalist'; + + for (const option of options) { + // Перебор значений для автозаполнения поля + + // Инициализация элемента со значением автозаполнения + const element = document.createElement('option'); + element.setAttribute('value', option); + + // Запись в список + datalist.appendChild(element); } } - }); - // Инициализация разделителя кнопки активации - let separator = document.createElement('div'); - separator.classList.add('settings_separated_row_extra'); - separator.checked = result === true || result === 1 || result === '1' ? true : false; + // Инициализация элемента-иконки + const label = document.createElement('div'); + label.classList.add('ui_toggler_label'); - // Инициализация кнопки активации - let button = document.createElement('div'); - button.classList.add('ui_toggler_wrap'); + // Инициализация названия + const header = document.createElement('div'); + header.classList.add('settings_separated_row_text'); - // Инициализация элемента-иконки кнопки активации - let checkbox = document.createElement('div'); - checkbox.classList.add('_ui_toggler', 'ui_toggler', '_settings_ienable'); - - settings.read(group + '_' + id).then(result => { - // Получены данные о значении настройки - - // Запись состояния - if (result) - checkbox.classList.add('on'); - }); - - // Инициализация элемента-иконки кнопки активации - let label = document.createElement('div'); - label.classList.add('ui_toggler_label'); - - // Инициализация названия кнопки активации - let header = document.createElement('div'); - header.classList.add('settings_separated_row_text'); - - // Инициализация текста названия кнопки активации - let title = document.createElement('div'); - title.classList.add('settings_separated_row_text_inner'); - title.innerText = name !== undefined && typeof name === 'string' ? name : id; - - // Инициализация архитектуры - button.appendChild(checkbox); - separator.appendChild(button); - wrap.appendChild(separator); - - if (description !== undefined && typeof description === 'string') { - // Получено описание - - // Инициализация текста описания кнопки активации - let text = document.createElement('div'); - text.classList.add('settings_separated_row_hint'); - text.innerText = description; + // Инициализация текста названия + const colonic = document.createElement('div'); + colonic.classList.add('settings_separated_row_text_inner'); + colonic.innerText = name !== undefined && typeof name === 'string' ? name : id; // Инициализация архитектуры - title.appendChild(text); + separator.appendChild(input); + if (typeof datalist === 'object') separator.appendChild(datalist); + wrap.appendChild(separator); + header.appendChild(colonic); + + if (typeof hint === 'string') { + // Получена подсказка + + // Инициализация текста-подсказки + const question = document.createElement('span'); + question.classList.add('hint_icon'); + question.setAttribute('data-title', hint); + question.setAttribute('onmouseover', 'showHint(this);'); + + // Инициализация архитектуры + colonic.appendChild(question); + } + + if (description !== undefined && typeof description === 'string') { + // Получено описание + + // Инициализация текста описания + const text = document.createElement('div'); + text.classList.add('settings_separated_row_hint'); + text.innerText = description; + + // Инициализация архитектуры + colonic.appendChild(text); + } + + // Инициализация архитектуры + wrap.appendChild(header); + + // Запись в блок + body.appendChild(wrap); + + return true; } + ); + } - // Инициализация архитектуры - header.appendChild(title); - wrap.appendChild(header); + return false; + }; + }, - // Запись в блок - body.appendChild(wrap); + /** + * Генерация HTML-элемента настройки с всплывающим списком + * + * @param {string} id Идентификатор + * @param {string|null} icon Иконка + * @param {string|null} name Название + * @param {string|null} description Описание + * @param {string|null} hint Всплывающая подсказка (иконка с вопросительным знаком возле верхнего колонтинула) + * @param {string|null} type + * @param {string|null} button + * @param {object|null} options Массив со строками для автозаполнения поля + * @param {string|null} onclick + * @param {string|null} onchange Код который выполняется после изменения состояния + * @param {string|null} oninput Код который выполняется при вводе текста + * + * @return {function} Функция для выполнения в генераторе группы + * + * @todo 1. Добавить проверку на существование нижнего колонтинула и записывать перед ним, вместо конца блока + */ + button(id, icon, name, description, hint, type = 'dropdown', value = 'Выбрать', onclick, onchange, oninput) { + // Инициализация ядра + const core = this.core; - return true; - }); + /** + * Запись в группу (подразумевается выполнение в функции генерирующей группу) + * + * @param {string} group Группа + * + * @return {bool} Статус выполнения + */ + return function (group) { + if (typeof id === 'string' && typeof icon === 'string' && typeof core === 'object' && typeof group === 'string') { + // Пройдена проверка входных параметров + + return core.blocks.fields.macroses.row(group, icon, + function (status, body, wrap, separator) { + wrap.addEventListener("input", fn => { + // Инициализация поля ввода + const input = wrap.getElementsByTagName('input')[0]; + + if (settings.write(group + '_' + id, input.value)) { + // Записано значение поля + + // Запуск анимации + settings.read(group + '_' + id).then(result => { + if (result === input.value) { + // Сохранены изменения + + // Запуск анимации + status.style.transition = '0.5s'; + status.style.opacity = 1; + setTimeout(fn => { + status.style.transition = '1.5s'; + status.style.opacity = 0; + }, 1000); + } + }); + } + }); + + // Инициализация элемента-кнопки + const button = document.createElement('a'); + button.innerText = value; + if (typeof onchange === 'string') button.setAttribute('onchange', onchange); + if (typeof oninput === 'string') button.setAttribute('oninput', oninput); + + if (type === 'dropdown') { + // Инициализация всплывающего списка + + // Инициализация элемента-кнопки + button.setAttribute('onclick', 'return Privacy.show(this, event, \'mail\');' + typeof onclick === 'string' ? ' ' + onclick : ''); + + // Инициализация элемента-списка + const dropdown = core.blocks.fields.macroses.dropdown(); + + + // Инициализация архитектуры + separator.appendChild(button); + } else if (type === 'popup') { + // Инициализация всплывающего окна + + // Инициализация элемента-кнопки + button.setAttribute('href', '#'); + button.setAttribute('onclick', 'return Settings.showGroupMessagesNotifyBox(event, \'settings\');' + typeof onclick === 'string' ? ' ' + onclick : ''); + + // Инициализация архитектуры + separator.appendChild(button); + } else if (type === 'button') { + // Инициализация кнопки + + // Инициализация элемента-кнопки + if (typeof onclick === 'string') button.setAttribute('onclick', onclick); + + // Инициализация архитектуры + separator.appendChild(button); + } + + // Инициализация элемента-иконки + const label = document.createElement('div'); + label.classList.add('ui_toggler_label'); + + // Инициализация названия + const header = document.createElement('div'); + header.classList.add('settings_separated_row_text'); + + // Инициализация текста названия + const colonic = document.createElement('div'); + colonic.classList.add('settings_separated_row_text_inner'); + colonic.innerText = name !== undefined && typeof name === 'string' ? name : id; + + // Инициализация архитектуры + separator.appendChild(button); + wrap.appendChild(separator); + header.appendChild(colonic); + + if (typeof hint === 'string') { + // Получена подсказка + + // Инициализация текста-подсказки + const question = document.createElement('span'); + question.classList.add('hint_icon'); + question.setAttribute('data-title', hint); + question.setAttribute('onmouseover', 'showHint(this);'); + + // Инициализация архитектуры + colonic.appendChild(question); + } + + if (description !== undefined && typeof description === 'string') { + // Получено описание + + // Инициализация текста описания + const text = document.createElement('div'); + text.classList.add('settings_separated_row_hint'); + text.innerText = description; + + // Инициализация архитектуры + colonic.appendChild(text); + } + + // Инициализация архитектуры + wrap.appendChild(header); + + // Запись в блок + body.appendChild(wrap); + + return true; + } + ); } return false;