diff --git a/CHANGELOG-EN.md b/CHANGELOG-EN.md deleted file mode 100644 index 8b13789..0000000 --- a/CHANGELOG-EN.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b13789..4df6e0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1,114 @@ +# 1.0.0-beta.3 (23 февраля, 2018) +## Расширение + +### Добавлено + +* Добавлены настройки. +* Добавлен профиль пользователя. + +### Изменено + +* Изменено название c "2ch+" на "2ch-helper". +* Изменен формат версии. Теперь не beta-дата, а 1.0.0-beta.версия-беты. + +## Проект + +### Добавлено + +* Добавлена документация для модулей. +* Добавлена информация в CHANGELOG. +* Добавлена информация в README. +* Добавлены модули: background-events.js, background-user-profile.js, settings-download.js, settings-iframe.js, settings-screenshot.js, settings.js, settings-iframe.css, settings.css. +* Добавлены страницы: settings-download.html, settings-screenshot.html. +* Добавлены библиотеки: bootstrap-slider.min.js, bootstrap-slider.min.css. +* Добавлен "persistent: false" в манифест. + +### Удалено + +* Changelog (EN версия). +* Readme (EN версия). +* License (RU версия). +* Удален временный коментарий из content-downloads.js. + +### Изменено + +* Большинство "then" изменено на "await". +* Незначительные изменения в коде. +* Изменена страница настроек. +* Изменен порядок загрузки скриптов. + +# 1.0.0-beta.2 (11 февраля, 2018) + +## Расширение + +### Изменено + +* Скачанные файлы имеют оригинальное имя. +* Скриншот постов имеет имя `posts.jpg`. +* Скриншот треда имеет имя `thread.jpg`. +* Во время создания скриншота все "ненужные" детали страницы скрываются. +* Если во время работы расширения произошла ошибка, то она появится в консоли браузера. + +## Проект + +### Добавлено + +* Changelog (RU и EN версия). +* Readme (EN версия). +* License (RU версия). + +### Удалено + +* удалена папка `libs` из `extension/interaction/js/`. + +### Изменено + +* Произведено разделение логики расширения на модули. + * Исчезли глобальные переменные. Теперь каждая переменная закреплена за собственным модулем. + * Общие функции или специфические функции перенесены в главный модуль API (`content-API.js`, `background-API.js`). +* Произведен рефакторинг всех скриптов. + * Большинство функций реализованы через `Promise`. + * Переменные `var` заменены на `const` и `let`. + * Изменена логика каждого модуля. + * Изменен формат сообщений между модулями. Теперь в сообщении обязательно должен содержаться тип сообщения. +* Модули скриншота полностью переписаны. + * Изменен алгоритм получения координат. + * Скриншоты делаются в формате JPEG c качеством 100%. + * Появилась обработка ошибок. + +### Улучшение производительности + +* Модули инжектятся в страницу не сразу, а по мере их необходимости. +* Память освобождается от мусора после создания скриншота. + +# 1.0.0-beta.1 (5 февраля, 2018) + +## Расширение + +### Новые функции + +* **скриншот**: + * создание скриншота треда. +* **загрузка**: + * скачивание изображений. + * скачивание видео. + * скачивание медиаконтента (изображения + видео). + * скачивание треда. + +## Проект + +### Изменено + +* Скрипты взаимодействия теперь находятся в папке `scripts`. + +# 1.0.0-beta.0 (3 февраля, 2018) + +Релиз проекта. + +## Расширение + +### Новые функции + +* **скриншот:** + * создание скриншота постов. diff --git a/LICENSE-RU b/LICENSE-RU deleted file mode 100644 index 8b13789..0000000 --- a/LICENSE-RU +++ /dev/null @@ -1 +0,0 @@ - diff --git a/README-EN.md b/README-EN.md deleted file mode 100644 index 8b13789..0000000 --- a/README-EN.md +++ /dev/null @@ -1 +0,0 @@ - diff --git a/README.md b/README.md index a8b8855..e02a6b4 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ -# 2ch-helper -Расширение для Google Chrome, которое упрощает взаимодействие с сайтом 2ch.hk +

+ 2ch-helper +

+ +

+ Расширение для браузера, которое облегчает взаимодействие с имиджбордой 2ch.hk. +

+ +
+ +## Содержание + +- [Возможности](#Возможности) +- [Системные требования](#Системные-требования) +- [Установка](#Установка) +- [Лицензия](#Лицензия) + +## Возможности + +- создание скриншота постов и треда. +- загрузка изображений, видео и треда. + +## Системные требования + +- Браузер: Google Chrome. + +## Установка + +**GitHub** + +1. Скачайте этот репозиторий. +2. Перейдите на вкладку . +3. Включите режим разработчика. +4. Нажмите на "Загрузить распакованное расширение...". +5. Выберите папку "extension". + +## Лицензия + +Исходный код находится под [лицензией MIT](https://github.com/Amaimersion/2ch-helper/blob/master/LICENSE "Лицензия"). \ No newline at end of file diff --git a/extension/interaction/js/scripts/background/background-API.js b/extension/interaction/js/scripts/background/background-API.js index 36f0238..0f29a96 100644 --- a/extension/interaction/js/scripts/background/background-API.js +++ b/extension/interaction/js/scripts/background/background-API.js @@ -1,11 +1,69 @@ +/** + * The common module for usage in others background modules. + * + * @module BackgroundAPI + */ function BackgroundAPI() {} +/** + * Settings of user. + * + * @memberof BackgroundAPI + * @static + * @type {Object} + */ +BackgroundAPI.userSettings = {}; + + +/** + * Clears a cashe of the background page. + * It just reload a page, but it's works. + * + * @memberof BackgroundAPI + * @static + */ BackgroundAPI.clearCashe = function() { window.location.reload(true); } +/** + * Gets an user settings from chome storage. + * After getting they will be setted to BackgroundAPI.userSettings. + * + * @memberof BackgroundAPI + * @static + * @async + */ +BackgroundAPI.getUserSettings = function() { + // what settings to receive. + const settings = [ + 'settings_screenshot', + 'settings_download' + ]; + + chrome.storage.sync.get(settings, (data) => { + this.userSettings = data; + }); +} + + +/** + * Injects a script into the page. + * + * @memberof BackgroundAPI + * @static + * @async + * + * @param {Object} options + * An options for execute. + * See https://developer.chrome.com/extensions/tabs#method-executeScript + * + * @returns {Promise} + * A promise for the inject that will resolve when injects are successfully completed. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ BackgroundAPI.injectScript = function(options) { return new Promise((resolve, reject) => { chrome.tabs.executeScript(options, (result) => { diff --git a/extension/interaction/js/scripts/background/background-downloads.js b/extension/interaction/js/scripts/background/background-downloads.js index 404fb4a..b98a476 100644 --- a/extension/interaction/js/scripts/background/background-downloads.js +++ b/extension/interaction/js/scripts/background/background-downloads.js @@ -1,6 +1,22 @@ +/** + * The module that handles download requests. + * + * @module BackgroundDownloads + */ function BackgroundDownloads() {} +/** + * Downloads a thread. + * + * @memberof BackgroundDownloads + * @static + * @async + * + * @returns {Promise} + * A promise for the download that will resolve when download are successfully completed. + * Resolve will contain nothing if success. + */ BackgroundDownloads.downloadThread = function() { return new Promise((resolve, reject) => { const query = {active: true, currentWindow: true}; @@ -10,44 +26,89 @@ BackgroundDownloads.downloadThread = function() { let name = (tabs[0].title || 'thread') + '.mhtml'; name = this.fixFileName(name); - chrome.pageCapture.saveAsMHTML(captureOptions, (mhtml) => { + chrome.pageCapture.saveAsMHTML(captureOptions, async (mhtml) => { const url = URL.createObjectURL(mhtml); const downloadOptions = { url: url, filename: name }; - this.download(downloadOptions).then(() => { - return resolve(); - }); + await this.download(downloadOptions); + + return resolve(); }); }); }); } +/** + * Downloads an images. + * + * @memberof BackgroundDownloads + * @static + * @async + * + * @param {Array} urls An urls for download. + * + * @returns {Promise} + * A promise for the download that will resolve when download are successfully completed. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ BackgroundDownloads.downloadImages = function(urls) { - return new Promise((resolve, reject) => { - this.downloadData(urls).then(() => { - return resolve(); - }, (error) => { - return reject(error); - }); + return new Promise(async (resolve, reject) => { + try { + await this.downloadData(urls); + } catch (error) { + return reject(error); + } + + return resolve(); }); } +/** + * Downloads a video. + * + * @memberof BackgroundDownloads + * @static + * @async + * + * @param {Array} urls An urls for download. + * + * @returns {Promise} + * A promise for the download that will resolve when download are successfully completed. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ BackgroundDownloads.downloadVideo = function(urls) { - return new Promise((resolve, reject) => { - this.downloadData(urls).then(() => { - return resolve(); - }, (error) => { - return reject(error); - }); + return new Promise(async (resolve, reject) => { + try { + await this.downloadData(urls); + } catch (error) { + return reject(error); + } + + return resolve(); }); } +/** + * Downloads a data. + * + * @memberof BackgroundDownloads + * @static + * @async + * + * @param {Object} options + * An options for download. + * See https://developer.chrome.com/extensions/downloads#method-download + * + * @returns {Promise} + * A promise for the download that will resolve when download are successfully completed. + * Resolve will contain the id of the new download item if success. + */ BackgroundDownloads.download = function(options) { return new Promise((resolve, reject) => { chrome.downloads.download(options, (downloadId) => { @@ -57,6 +118,34 @@ BackgroundDownloads.download = function(options) { } +/** + * Downloads a data of the urls. + * + * @memberof BackgroundDownloads + * @static + * @async + * + * @param {Array} urls + * The urls for download. + * + * @param {String} [fileName] + * A name of the file. + * By default this is determined based on the url. + * If cannot be determined, then 'undefined'. + * + * @param {String} [fileFormat] + * A format of the file. + * By default this is determined based on the url. + * If cannot be determined, then ''. + * + * @param {Object} [downloadOptions] + * An options for download. + * See https://developer.chrome.com/extensions/downloads#method-download + * + * @returns {Promise} + * A promise for the download that will resolve when download are successfully completed. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ BackgroundDownloads.downloadData = function(urls, fileName, fileFormat, downloadOptions) { return new Promise((resolve, reject) => { downloadOptions = downloadOptions || {}; @@ -65,25 +154,27 @@ BackgroundDownloads.downloadData = function(urls, fileName, fileFormat, download for (let url of urls) { downloadPromise = downloadPromise.then(() => { return new Promise((res) => { - const name = ( - fileName || - this.determineFileName(url) || - 'undefined' - ); const format = ( fileFormat || this.determineFileFormat(url) || '' ); + let name = ''; + + if (BackgroundAPI.userSettings.settings_download.autoDetectionName) { + name = this.determineFileName(url) || 'undefined'; + } else { + name = BackgroundAPI.userSettings.settings_download.fileName; + } downloadOptions.url = url; downloadOptions.filename = name + format; - + window.setTimeout(() => { this.download(downloadOptions).then(() => { return res(); }); - }, 500); + }, BackgroundAPI.userSettings.settings_download.delay); }); }); } @@ -100,6 +191,22 @@ BackgroundDownloads.downloadData = function(urls, fileName, fileFormat, download } +/** + * Determines the file name based on the url. + * + * @memberof BackgroundDownloads + * @static + * + * @param {String} url + * + * @returns {String} + * The name of the url. + * If cannot be determined, then undefined. + * + * @example + * // returns '123'. + * BackgroundDownloads.determineFileName('https://2ch.hk/pr/src/987/123.jpg'). + */ BackgroundDownloads.determineFileName = function(url) { const lastIndex = url.lastIndexOf('.'); const preLastIndex = url.lastIndexOf('/'); @@ -112,6 +219,22 @@ BackgroundDownloads.determineFileName = function(url) { } +/** + * Determines the file format based on the url. + * + * @memberof BackgroundDownloads + * @static + * + * @param {String} url + * + * @returns {String} + * The format of the url. + * If cannot be determined, then undefined. + * + * @example + * // returns '.jpg'. + * BackgroundDownloads.determineFileName('https://2ch.hk/pr/src/987/123.jpg'). + */ BackgroundDownloads.determineFileFormat = function(url) { const lastIndex = url.lastIndexOf('.'); @@ -123,6 +246,21 @@ BackgroundDownloads.determineFileFormat = function(url) { } +/** + * Removes invalid characters from the file name. + * + * @memberof BackgroundDownloads + * @static + * + * @param {String} name + * A file name for fix. + * + * @param {String} [char=''] + * A char for replace. Defaults to ''. + * + * @returns {String} + * A valid file name. + */ BackgroundDownloads.fixFileName = function(name, char) { return name.replace(/[\\\/\:\*\?\"\<\>\|]/g, char || ''); } diff --git a/extension/interaction/js/scripts/background/background-events.js b/extension/interaction/js/scripts/background/background-events.js new file mode 100644 index 0000000..d3ae234 --- /dev/null +++ b/extension/interaction/js/scripts/background/background-events.js @@ -0,0 +1,46 @@ +/** + * The module that handles background events. + * An events that gets handling: first install, DOMContentLoaded. + * + * @module BackgroundEvents + */ +function BackgroundEvents() {} + + +/** + * Handles DOMContentLoaded event. + * + * @memberof BackgroundEvents + * @static + */ +BackgroundEvents.DOMContentLoaded = function() { + BackgroundAPI.getUserSettings(); +} + + +/** + * Handles first install event. + * + * @memberof BackgroundEvents + * @static + */ +BackgroundEvents.firstInstall = function() { + UserProfile.createProfile(); +} + + +/* Sets the events. */ + + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', BackgroundEvents.DOMContentLoaded); +} else { + BackgroundEvents.DOMContentLoaded(); +} + + +chrome.runtime.onInstalled.addListener((details) => { + if (details.reason === 'install') { + BackgroundEvents.firstInstall(); + } +}); diff --git a/extension/interaction/js/scripts/background/background-on-message.js b/extension/interaction/js/scripts/background/background-on-message.js index 7358b9c..059830f 100644 --- a/extension/interaction/js/scripts/background/background-on-message.js +++ b/extension/interaction/js/scripts/background/background-on-message.js @@ -1,6 +1,25 @@ +/** + * The module that handles messages. + * Can handle messages of the following types: 'command'. + * + * @module BackgroundMessage + */ function BackgroundMessage() {} +/** + * Handles messages. + * + * @memberof BackgroundMessage + * @static + * + * @param {Object} request + * @param {Object} sender + * @param {Object} sendResponse + * + * @returns {Boolean} + * Returns true because there will be an asynchronous response. + */ BackgroundMessage.onMessage = function(request, sender, sendResponse) { if (request.type === 'command') { BackgroundMessage.commandHandler(request, sendResponse); @@ -12,93 +31,132 @@ BackgroundMessage.onMessage = function(request, sender, sendResponse) { } -BackgroundMessage.commandHandler = function(request, sendResponse) { +/** + * Handles command type messages. + * + * @memberof BackgroundMessage + * @static + * @async + * + * @param {Object} request + * @param {Object} sendResponse + * + * @throws {Error} Throws an error if occurs. + */ +BackgroundMessage.commandHandler = async function(request, sendResponse) { const command = request.command; if (command === 'downloadThread') { - const promise = BackgroundDownloads.downloadThread(); - - promise.then(() => { - sendResponse({status: true}); - }, (error) => { + try { + await BackgroundDownloads.downloadThread(); + } catch (error) { sendResponse({status: false, error: error}); throw error; - }); + } - } else if (command === 'downloadImages') { - const promise = BackgroundDownloads.downloadImages(request.data); + sendResponse({status: true}); - promise.then(() => { - sendResponse({status: true}); - }, (error) => { + } else if (command === 'downloadImages') { + try { + await BackgroundDownloads.downloadImages(request.data); + } catch (error) { sendResponse({status: false, error: error}); throw error; - }); + } - } else if (command === 'downloadVideo') { - const promise = BackgroundDownloads.downloadVideo(request.data); + sendResponse({status: true}); - promise.then(() => { - sendResponse({status: true}); - }, (error) => { + } else if (command === 'downloadVideo') { + try { + await BackgroundDownloads.downloadVideo(request.data); + } catch (error) { sendResponse({status: false, error: error}); throw error; - }); + } - } else if (command === "createScreenshot") { - const promise = BackgroundScreenshot.createScreenshot(request.coordinate); + sendResponse({status: true}); - promise.then(() => { - sendResponse({status: true}); - }, (error) => { + } else if (command === "createScreenshot") { + try { + await BackgroundScreenshot.createScreenshot(request.coordinate); + } catch (error) { BackgroundAPI.clearCashe(); sendResponse({status: false}); throw error; - }) + } + + sendResponse({status: true}); } else if (command === "createPostsImage") { - const promise = BackgroundScreenshot.createPostsImage(); + let uri = ''; - promise.then((uri) => { - BackgroundDownloads.download({url: uri, filename: 'posts.jpg'}); - BackgroundAPI.clearCashe(); - sendResponse({status: true}); - }, (error) => { + try { + uri = await BackgroundScreenshot.createPostsImage(); + } catch (error) { BackgroundAPI.clearCashe(); sendResponse({status: false, error: error}); throw error; - }); + } + + const filename = ( + BackgroundAPI.userSettings.settings_screenshot.fileNamePosts + + '.' + + BackgroundAPI.userSettings.settings_screenshot.format + ); + + BackgroundDownloads.download({url: uri, filename: filename}); + BackgroundAPI.clearCashe(); + sendResponse({status: true}); } else if (command === "createThreadImage") { - const promise = BackgroundScreenshot.createThreadImage(); + let uri = ''; - promise.then((uri) => { - BackgroundDownloads.download({url: uri, filename: 'thread.jpg'}); - BackgroundAPI.clearCashe(); - sendResponse({status: true}); - }, (error) => { + try { + uri = await BackgroundScreenshot.createThreadImage(); + } catch (error) { BackgroundAPI.clearCashe(); sendResponse({status: false, error: error}); throw error; - }); + } + + const filename = ( + BackgroundAPI.userSettings.settings_screenshot.fileNameThread + + '.' + + BackgroundAPI.userSettings.settings_screenshot.format + ); + + BackgroundDownloads.download({url: uri, filename: filename}); + BackgroundAPI.clearCashe(); } else if (command === 'injectScript') { const options = { file: request.path }; - const promise = BackgroundAPI.injectScript(options); - - promise.then(() => { - sendResponse({status: true}); - }, (error) => { + try { + await BackgroundAPI.injectScript(options); + } catch (error) { sendResponse({status: false, error: error}); throw error; - }); + } + + sendResponse({status: true}); } } +/** + * Handles unknown type messages. + * + * @memberof BackgroundMessage + * @static + * + * @param {Object} request + * @param {Object} sender + * @param {Object} sendResponse + * + * @throws {Error} Throws an error with request information. + */ BackgroundMessage.errorHandler = function(request, sender, sendResponse) { console.log( 'An unknown request was received.\n', @@ -122,4 +180,7 @@ BackgroundMessage.errorHandler = function(request, sender, sendResponse) { } +/** + * Sets a message handler. + */ chrome.runtime.onMessage.addListener(BackgroundMessage.onMessage); diff --git a/extension/interaction/js/scripts/background/background-screenshot.js b/extension/interaction/js/scripts/background/background-screenshot.js index cf6bbc4..a26e12d 100644 --- a/extension/interaction/js/scripts/background/background-screenshot.js +++ b/extension/interaction/js/scripts/background/background-screenshot.js @@ -1,43 +1,109 @@ +/** + * The module that handles screenshot requests. + * + * @module BackgroundScreenshot + */ function BackgroundScreenshot() {} +/** + * Data for further handling. + * Contains object with next properties: 'coordinate', 'uri'. + * Coordinate is position of needed region to crop. + * Uri is image to crop. + * + * @memberof BackgroundScreenshot + * @static + * @type {Array} + */ BackgroundScreenshot.data = []; + + +/** + * Images for further handling. + * + * @memberof BackgroundScreenshot + * @static + * @type {Array} + */ BackgroundScreenshot.images = []; +/** + * Clears BackgroundScreenshot.data and BackgroundScreenshot.images. + * + * @memberof BackgroundScreenshot + * @static + */ BackgroundScreenshot.clearData = function() { this.data = []; this.images = []; } +/** + * Creates a screenshot of current window and adds + * coordinate and URI (screenshot) to BackgroundScreenshot.data. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @param {ContentScreenshot.Coordinate} coordinate + * Position of needed region to crop. + * + * @returns {Promise} + * A promise for the create that will resolve when create are successfully completed. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ BackgroundScreenshot.createScreenshot = function(coordinate) { - return new Promise((resolve, reject) => { - const screenshotPromise = this.createTabScreenshot(); + return new Promise(async (resolve, reject) => { + let uri = ''; - screenshotPromise.then((uri) => { - this.data.push({ - coordinate: coordinate, - uri: uri - }); - - return resolve(); - }, () => { - this.data = []; + try { + uri = await this.createTabScreenshot(); + } catch (e) { + this.clearData(); const error = new Error(); error.message = 'Failed to create screenshot.'; return reject(error); + } + + this.data.push({ + coordinate: coordinate, + uri: uri }); + + return resolve(); }); } +/** + * Creates a screenshot of current window. + * Screenshot will have JPEG format and 100% quality. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @param {Object} options + * An options for capture. + * See https://developer.chrome.com/extensions/tabs#method-captureVisibleTab + * + * @returns {Promise} + * A promise for the create that will resolve when create are successfully completed. + * Resolve will contain uri if success. + */ BackgroundScreenshot.createTabScreenshot = function(options) { - options = options || {format: 'jpeg', quality: 100}; - return new Promise((resolve, reject) => { + options = options || { + format: BackgroundAPI.userSettings.settings_screenshot.format, + quality: BackgroundAPI.userSettings.settings_screenshot.quality + }; + chrome.tabs.captureVisibleTab(options, (uri) => { return resolve(uri); }); @@ -45,65 +111,129 @@ BackgroundScreenshot.createTabScreenshot = function(options) { } -BackgroundScreenshot.createPostsImage = function() { - return new Promise((resolve, reject) => { - const createPromise = this.createObjectImage(); - - createPromise.then((uri) => { - return resolve(uri); - }, (error) => { +/** + * Creates full image of posts. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @returns {Promise} + * A promise for the create that will resolve when create are successfully completed. + * Resolve will contain uri if success, otherwise reject will contain an error. + */ +BackgroundScreenshot.createPostsImage = async function() { + return new Promise(async (resolve, reject) => { + let uri = ''; + + try { + uri = await this.createObjectImage(); + } catch (error) { return reject(error); - }); + } + + return resolve(uri); }); } +/** + * Creates full image of thread. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @returns {Promise} + * A promise for the create that will resolve when create are successfully completed. + * Resolve will contain uri if success, otherwise reject will contain an error. + */ BackgroundScreenshot.createThreadImage = function() { - return new Promise((resolve, reject) => { - const createPromise = this.createObjectImage(); + return new Promise(async (resolve, reject) => { + let uri = ''; - createPromise.then((uri) => { - return resolve(uri); - }, (error) => { + try { + uri = await this.createObjectImage(); + } catch (error) { return reject(error); - }); + } + + return resolve(uri); }); } +/** + * Creates full image of BackgroundScreenshot.data. + * Template for BackgroundScreenshot.createPostsImage and + * BackgroundScreenshot.createThreadImage. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @returns {Promise} + * A promise for the create that will resolve when create are successfully completed. + * Resolve will contain uri if success, otherwise reject will contain an error. + */ BackgroundScreenshot.createObjectImage = function() { - return new Promise((resolve, reject) => { - const createPromise = this.createImage(); + return new Promise(async (resolve, reject) => { + let uri = ''; - createPromise.then((uri) => { - this.clearData(); - return resolve(uri); - }, (error) => { + try { + uri = await this.createImage(); + } catch (error) { this.clearData(); return reject(error); - }); + } + + this.clearData(); + + return resolve(uri); }); } +/** + * Creates full image of BackgroundScreenshot.data. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @returns {Promise} + * A promise for the create that will resolve when create are successfully completed. + * Resolve will contain uri if success, otherwise reject will contain an error. + */ BackgroundScreenshot.createImage = function() { - return new Promise((resolve, reject) => { - const dataLoadPromise = this.createImagesOfData(); - - dataLoadPromise.then(() => { - const fullImageURI = this.createFullImageOfImages(); - - return resolve(fullImageURI); - }, () => { + return new Promise(async (resolve, reject) => { + try { + await this.createImagesOfData(); + } catch (e) { const error = new Error(); error.message = 'Failed to create full image.'; return reject(error); - }); + } + + const fullImageURI = this.createFullImageOfImages(); + + return resolve(fullImageURI); }); } +/** + * Creates crop images of BackgroundScreenshot.data. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @returns {Promise} + * A promise for the create that will resolve when create are successfully completed. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ BackgroundScreenshot.createImagesOfData = function() { return new Promise((resolve, reject) => { let promise = Promise.resolve(); @@ -116,6 +246,7 @@ BackgroundScreenshot.createImagesOfData = function() { loadPromise.then((image) => { const coordinate = data.coordinate; + // if it's a posts coordinates. if (coordinate.constructor === Array) { const handlerPromise = this.arrayHandler( image, @@ -124,7 +255,10 @@ BackgroundScreenshot.createImagesOfData = function() { handlerPromise.then(() => { return res(); + }, () => { + return rej(); }); + // if it's a thread coordinate. } else { const handlerPromise = this.singleHandler( image, @@ -133,6 +267,8 @@ BackgroundScreenshot.createImagesOfData = function() { handlerPromise.then(() => { return res(); + }, () => { + return rej(); }); } }); @@ -152,6 +288,23 @@ BackgroundScreenshot.createImagesOfData = function() { } +/** + * Handles array coordinates. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @param {HTMLImageElement} image + * An image to crop. + * + * @param {Array} coordinates + * A coordinates to crop. + * + * @returns {Promise} + * A promise for the handle that will resolve when handle are successfully completed. + * Resolve will contain nothing if success. + */ BackgroundScreenshot.arrayHandler = function(image, coordinates) { return new Promise((resolve, reject) => { let handlePromise = Promise.resolve(); @@ -166,6 +319,8 @@ BackgroundScreenshot.arrayHandler = function(image, coordinates) { singlePromise.then(() => { return res(); + }, () => { + return rej(); }); }); }); @@ -173,26 +328,56 @@ BackgroundScreenshot.arrayHandler = function(image, coordinates) { handlePromise.then(() => { return resolve(); + }, () => { + return reject(); }); }); } +/** + * Handles single coordinate. + * Cropped image will be added to BackgroundScreenshot.images. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @param {HTMLImageElement} image + * An image to crop. + * + * @param {ContentScreenshot.Coordinate} coordinate + * A coordinate to crop. + * + * @returns {Promise} + * A promise for the handle that will resolve when handle are successfully completed. + * Resolve will contain nothing if success. + */ BackgroundScreenshot.singleHandler = function(image, coordinate) { - return new Promise((resolve, reject) => { - const cropPromise = this.cropImage( - image, - coordinate - ); + return new Promise(async (resolve, reject) => { + let cropImage; - cropPromise.then((image) => { - this.images.push(image); - return resolve(); - }); + try { + cropImage = await this.cropImage(image, coordinate); + } catch (e) { + return reject(); + } + + this.images.push(cropImage); + + return resolve(); }); } +/** + * Creates full image of BackgroundScreenshot.images. + * + * @memberof BackgroundScreenshot + * @static + * + * @returns {String} URL of the full image. + */ BackgroundScreenshot.createFullImageOfImages = function() { let height = 0; @@ -213,14 +398,35 @@ BackgroundScreenshot.createFullImageOfImages = function() { y += image.height; } - const url = canvas.toDataURL('image/jpeg', 1); + const url = canvas.toDataURL( + 'image/' + BackgroundAPI.userSettings.settings_screenshot.format, + BackgroundAPI.userSettings.settings_screenshot.quality / 100 + ); return url; } +/** + * Crops the image by coordinate. + * Cropped image will have JPEG format and 100% quality. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @param {HTMLImageElement} image + * An image to crop. + * + * @param {ContentScreenshot.Coordinate} coordinate + * A coordinate to crop. + * + * @returns {Promise} + * A promise for the crop that will resolve when crop are successfully completed. + * Resolve will contain cropped image if success. + */ BackgroundScreenshot.cropImage = function(image, coordinate) { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); @@ -239,16 +445,31 @@ BackgroundScreenshot.cropImage = function(image, coordinate) { coordinate.height ); - const url = canvas.toDataURL('image/jpeg', 1); - const loadPromise = this.loadImage(url); - - loadPromise.then((image) => { - return resolve(image); - }) + const url = canvas.toDataURL( + 'image/' + BackgroundAPI.userSettings.settings_screenshot.format, + BackgroundAPI.userSettings.settings_screenshot.quality / 100 + ); + const croppedImage = await this.loadImage(url); + + return resolve(croppedImage); }); } +/** + * Loads the image from src. + * + * @memberof BackgroundScreenshot + * @static + * @async + * + * @param {String} src + * An url for load. + * + * @returns {Promise} + * A promise for the load that will resolve when load are successfully completed. + * Resolve will contain loaded image if success. + */ BackgroundScreenshot.loadImage = function(src) { return new Promise((resolve, reject) => { const image = new Image(); diff --git a/extension/interaction/js/scripts/background/background-user-profile.js b/extension/interaction/js/scripts/background/background-user-profile.js new file mode 100644 index 0000000..b08546d --- /dev/null +++ b/extension/interaction/js/scripts/background/background-user-profile.js @@ -0,0 +1,67 @@ +/** + * The module that handles user profile operations. + * User profile works through chrome.storage.sync. + * + * @module UserProfile + */ +function UserProfile() {} + + +/** + * Default profile of user. + * Sets when the first installation of the extension was performed. + * + * @memberof UserProfile + * @static + * @type {Object} + */ +UserProfile.defaultProfile = { + settings_screenshot: { + fileNamePosts: 'posts', + fileNameThread: 'thread', + format: 'jpeg', + quality: 100, + delay: 500 + }, + + settings_download: { + autoDetectionName: true, + userName: false, + fileName: '', + delay: 500 + } +}; + + +/** + * Creates user profile. + * + * @memberof UserProfile + * @static + */ +UserProfile.createProfile = function() { + chrome.storage.sync.set(this.defaultProfile); +} + + +/** + * Delets user profile. + * + * @memberof UserProfile + * @static + */ +UserProfile.deleteProfile = function() { + chrome.storage.sync.clear(); +} + + +/** + * Recreates user profile. + * + * @memberof UserProfile + * @static + */ +UserProfile.recreateProfile = function() { + this.deleteProfile(); + this.createProfile(); +} diff --git a/extension/interaction/js/scripts/content/content-API.js b/extension/interaction/js/scripts/content/content-API.js index 6c5c449..bf54220 100644 --- a/extension/interaction/js/scripts/content/content-API.js +++ b/extension/interaction/js/scripts/content/content-API.js @@ -1,6 +1,28 @@ +/** + * The common module for usage in others content modules. + * + * @module ContentAPI + */ function ContentAPI() {} +/** + * Settings of user. + * + * @memberof ContentAPI + * @static + * @type {Object} + */ +ContentAPI.userSettings = {}; + + +/** + * Information about other content modules. + * + * @memberof ContentAPI + * @static + * @type {Object} + */ ContentAPI.anotherAPI = { 'screenshot': { injected: false, @@ -40,28 +62,55 @@ ContentAPI.anotherAPI = { }; +/** + * Injects another module and executes the method of that module. + * + * @memberof ContentAPI + * @static + * @async + * + * @param {String} name + * A name of another content module. + * @param {String} method + * A method of another content module. + * + * @returns {Promise} + * A promise for the execute that will resolve when injects and executes are successfully completed. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ ContentAPI.executeAnotherAPI = function(name, method) { - return new Promise((resolve, reject) => { - const injectPromise = this.injectAnotherAPI(name); - - injectPromise.then(() => { - const executePromise = this.executeAnotherAPIMethod( - name, - method - ); + return new Promise(async (resolve, reject) => { + try { + await this.injectAnotherAPI(name); + } catch (error) { + return reject(error); + } - executePromise.then(() => { - return resolve(); - }, (error) => { - return reject(error); - }); - }, (error) => { + try { + await this.executeAnotherAPIMethod(name, method); + } catch (error) { return reject(error); - }); + } + + return resolve(); }); } +/** + * Injects another module into the page. + * + * @memberof ContentAPI + * @static + * @async + * + * @param {String} name + * A name of another content module. + * + * @returns {Promise} + * A promise for the injection that will resolve when the module will injects. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ ContentAPI.injectAnotherAPI = function(name) { return new Promise((resolve, reject) => { const status = this.anotherAPI[name].injected; @@ -94,6 +143,22 @@ ContentAPI.injectAnotherAPI = function(name) { } +/** + * Executes a method of another module. + * + * @memberof ContentAPI + * @static + * @async + * + * @param {String} name + * A name of another content module. + * @param {String} method + * A method of another content module. + * + * @returns {Promise} + * A promise for the execute that will resolve when the method will starts. + * Resolve will contain nothing if success, otherwise reject will contain an error. + */ ContentAPI.executeAnotherAPIMethod = function(name, method) { return new Promise((resolve, reject) => { const anotherAPIMethod = this.anotherAPI[name].methods[method]; @@ -115,6 +180,38 @@ ContentAPI.executeAnotherAPIMethod = function(name, method) { } +/** + * Gets an user settings from chome storage. + * After getting they will be setted to ContentAPI.userSettings. + * + * @memberof ContentAPI + * @static + * @async + */ +ContentAPI.getUserSettings = function() { + // what settings to receive. + const settings = [ + 'settings_screenshot' + ]; + + chrome.storage.sync.get(settings, (data) => { + this.userSettings = data; + }); +} + + +/** + * Gets a thread. Search occurs by id of a thread. + * + * @memberof ContentAPI + * @static + * + * @param {String} [id=posts-form] + * An id of thread. Defaults to 'posts-form'. + * + * @returns {HTMLElement} + * A thread if it was finded, otherwise null. + */ ContentAPI.getThread = function(id) { return document.getElementById(id || 'posts-form'); } @@ -123,15 +220,18 @@ ContentAPI.getThread = function(id) { /** * Gets a parent of the HTML element. * +* @memberof ContentAPI +* @static + * @param {HTMLElement} element * A beginning element. * -* @param {function(HTMLElement) => Boolean} condition -* A condition to complete. +* @param {function(HTMLElement) => Boolean} [condition] +* A condition to complete. Defaults does not affect the search. * * @returns {HTMLElement} -* If condition was not declared, then document will be return. -* Otherwise element which satisfies condition will be return. +* If the condition was not declared, then document will be return. +* Otherwise an element which satisfies the condition will be return. */ ContentAPI.getParent = function(element, condition) { condition = condition || function(element) {return false;}; @@ -146,6 +246,18 @@ ContentAPI.getParent = function(element, condition) { } +/** + * Sends a message to the background scripts. + * + * @memberof ContentAPI + * @static + * + * @param {Object} message + * A message for sending. + * + * @param {function(Object)} [callback] + * A callback that handle the response. + */ ContentAPI.sendMessageToBackground = function(message, callback) { callback = callback || function() {}; diff --git a/extension/interaction/js/scripts/content/content-DOMContentLoaded.js b/extension/interaction/js/scripts/content/content-DOMContentLoaded.js index 9ca52cc..f9eada3 100644 --- a/extension/interaction/js/scripts/content/content-DOMContentLoaded.js +++ b/extension/interaction/js/scripts/content/content-DOMContentLoaded.js @@ -1,20 +1,48 @@ +/** + * The module that run a main function when + * a page has the status 'DOMContentLoaded'. + * + * @module ContentDOMContentLoaded + */ function ContentDOMContentLoaded() {} +/** + * Runs when a page has the status 'DOMContentLoaded'. + * + * @memberof ContentDOMContentLoaded + * @static + */ ContentDOMContentLoaded.main = function() { this.bindCheckboxes(); + ContentAPI.getUserSettings(); } -ContentDOMContentLoaded.bindCheckboxes = function() { +/** + * Binds events to checkboxes on a page. + * + * @memberof ContentDOMContentLoaded + * @static + * + * @param {String} [checkboxesSelector=input[type="checkbox"]] + * A query selector for finding checkboxes. + * Defaults to 'input[type="checkbox"]'. + */ +ContentDOMContentLoaded.bindCheckboxes = function(checkboxesSelector) { + checkboxesSelector = checkboxesSelector || 'input[type="checkbox"]'; const thread = ContentAPI.getThread(); - const checkboxes = thread.querySelectorAll('input[type="checkbox"]'); + const checkboxes = thread.querySelectorAll(checkboxesSelector); + ContentPageElements.addEventsToCheckboxes(checkboxes); } +/** + * Adds event listener to the page. + */ if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', main); + document.addEventListener('DOMContentLoaded', ContentDOMContentLoaded.main); } else { ContentDOMContentLoaded.main(); } diff --git a/extension/interaction/js/scripts/content/content-downloads.js b/extension/interaction/js/scripts/content/content-downloads.js index c57624c..1353110 100644 --- a/extension/interaction/js/scripts/content/content-downloads.js +++ b/extension/interaction/js/scripts/content/content-downloads.js @@ -1,28 +1,65 @@ +/** + * The module that handles download requests. + * + * @module ContentDownloads + */ function ContentDownloads() {} +/** + * Downloads images. + * + * @memberof ContentDownloads + * @static + * @async + * + * @param {function(Object)} [callback] + * A callback that executes after download and handles the response. + */ ContentDownloads.downloadImages = function(callback) { callback = callback || function() {}; this.processDownload( 'img.preview:not(.webm-file)', 'downloadImages', + null, callback ); } +/** + * Downloads video. + * + * @memberof ContentDownloads + * @static + * @async + * + * @param {function(Object)} [callback] + * A callback that executes after download and handles the response. + */ ContentDownloads.downloadVideo = function(callback) { callback = callback || function() {}; this.processDownload( 'img.webm-file', 'downloadVideo', + null, callback ); } +/** + * Downloads media content. + * + * @memberof ContentDownloads + * @static + * @async + * + * @param {function(Object)} [callback] + * A callback that executes after download and handles the response. + */ ContentDownloads.downloadMedia = function(callback) { callback = callback || function() {}; @@ -32,30 +69,57 @@ ContentDownloads.downloadMedia = function(callback) { } +/** + * Downloads thread. + * + * @memberof ContentDownloads + * @static + * @async + * + * @param {function(Object)} [callback] + * A callback that executes after download and handles the response. + */ ContentDownloads.downloadThread = function(callback) { callback = callback || function() {}; const message = {type: 'command', command: 'downloadThread'}; - ContentAPI.sendMessageToBackground(message, () => { - callback(); + ContentAPI.sendMessageToBackground(message, (response) => { + callback(response); }); } -// я не указываю параметр condition. -// если в странице изменится структура, -// то этот параметер может понадобиться. -ContentDownloads.processDownload = function(query, command, callback) { +/** + * Downloads an elements. + * + * @memberof ContentDownloads + * @static + * @async + * + * @param {String} selector + * A query selector for getting preview elements. + * + * @param {String} command + * A command that executes download process in background script. + * Can be 'downloadThread', or 'downloadImages', or 'downloadVideo'. + * + * @param {function(HTMLElement) => Boolean} [condition] + * A condition for getting element href. + * Defaults to (return element.tagName === 'A'). + * + * @param {function(Object)} [callback] + * A callback that executes after download and handles the response. + */ +ContentDownloads.processDownload = function(selector, command, condition, callback) { const thread = ContentAPI.getThread(); - let previewElements = thread.querySelectorAll(query); - const fullElements = []; + let previewElements = thread.querySelectorAll(selector); - for (let element of previewElements) { - let fullElement = ContentAPI.getParent(element, (elem) => { - return (elem.tagName === 'A'); - }); + const fullElementsHrefs = []; + condition = condition || function(element) {return element.tagName === 'A'}; - fullElements.push(fullElement.href); + for (let element of previewElements) { + let fullElement = ContentAPI.getParent(element, condition); + fullElementsHrefs.push(fullElement.href); } previewElements = []; @@ -63,91 +127,10 @@ ContentDownloads.processDownload = function(query, command, callback) { const message = { type: 'command', command: command, - data: fullElements + data: fullElementsHrefs }; ContentAPI.sendMessageToBackground(message, (response) => { callback(response); }); } - - -// It is for archiving files into a .zip archive. -// However, it is sucks on a large pages. -// The current version is very raw and try not to use it. -// I'll turn off it for now. -// So, someday i either modify it or delete it. -/* -function downloadImagesZip() { - var thread = document.getElementById('posts-form'); - - var previewImages = thread.getElementsByTagName('img'); - var fullImages = []; - var i = 0; - - while (i != previewImages.length) { - var fullImage = ContentAPI.getParent(previewImages[i], function(element) { - return element.tagName === 'A'; - }); - - fullImages[i] = fullImage.href; - i++; - } - - previewImages = null; - var zip = new JSZip(); - i = 0; - var num = 0; - - while (i != fullImages.length) { - toDataURL(fullImages[i], 'image/jpeg', function(uri) { - zip.file(String(num) + '.png', uri.slice(22), {base64: true}); - num++; - - if (num === fullImages.length) { - fullImages = null; - - if (JSZip.support.uint8array) { - promise = zip.generateAsync({type : 'blob'}); - } else { - promise = zip.generateAsync({type : 'string'}); - } - - promise.then(function(blob) { - var url = URL.createObjectURL(blob); - ContentAPI.sendMessageToBackground({command: 'downloadZip', url: url}); - }); - } - }); - - i++; - } -} - -function toDataURL(src, outputFormat, callback) { - var img = new Image(); - img.crossOrigin = 'Anonymous'; - - img.onload = function() { - var canvas = document.createElement('CANVAS'); - var ctx = canvas.getContext('2d'); - var dataURL; - canvas.height = this.naturalHeight; - canvas.width = this.naturalWidth; - ctx.drawImage(this, 0, 0); - dataURL = canvas.toDataURL(outputFormat, 1.0); - callback(dataURL); - }; - - img.src = src; -} - - -// function to create a file in the HTML5 temporary filesystem -function createFile(filename, callback) { - webkitRequestFileSystem(TEMPORARY, 4 * 1024 * 1024, function(fs) { - fs.root.getFile(filename, { create : true }, callback); - }); - } - -*/ diff --git a/extension/interaction/js/scripts/content/content-on-message.js b/extension/interaction/js/scripts/content/content-on-message.js index aafa2ae..d77adf9 100644 --- a/extension/interaction/js/scripts/content/content-on-message.js +++ b/extension/interaction/js/scripts/content/content-on-message.js @@ -1,6 +1,25 @@ +/** + * The module that handles messages. + * Can handle messages of the following types: 'API'. + * + * @module ContentMessage + */ function ContentMessage() {} +/** + * Handles messages. + * + * @memberof ContentMessage + * @static + * + * @param {Object} request + * @param {Object} sender + * @param {Object} sendResponse + * + * @returns {Boolean} + * Returns true because there will be an asynchronous response. + */ ContentMessage.onMessage = function(request, sender, sendResponse) { if (request.type === 'API') { ContentMessage.APIHandler(request, sendResponse); @@ -12,21 +31,45 @@ ContentMessage.onMessage = function(request, sender, sendResponse) { } -ContentMessage.APIHandler = function(request, sendResponse) { - const promise = ContentAPI.executeAnotherAPI( - request.name, - request.method - ); - - promise.then(() => { - sendResponse({status: true}); - }, (error) => { +/** + * Handles API type messages. + * + * @memberof ContentMessage + * @static + * @async + * + * @param {Object} request + * @param {Object} sendResponse + * + * @throws {Error} Throws an error if occurs. + */ +ContentMessage.APIHandler = async function(request, sendResponse) { + try { + await ContentAPI.executeAnotherAPI( + request.name, request.method + ); + } + catch (error) { sendResponse({status: false, error: error}); throw error; - }); + } + + sendResponse({status: true}); } +/** + * Handles unknown type messages. + * + * @memberof ContentMessage + * @static + * + * @param {Object} request + * @param {Object} sender + * @param {Object} sendResponse + * + * @throws {Error} Throws an error with request information. + */ ContentMessage.errorHandler = function(request, sender, sendResponse) { console.log( 'An unknown request was received.\n', @@ -50,4 +93,7 @@ ContentMessage.errorHandler = function(request, sender, sendResponse) { } +/** + * Sets a message handler. + */ chrome.runtime.onMessage.addListener(ContentMessage.onMessage); diff --git a/extension/interaction/js/scripts/content/content-page-elements.js b/extension/interaction/js/scripts/content/content-page-elements.js index 6de0488..3c0bf33 100644 --- a/extension/interaction/js/scripts/content/content-page-elements.js +++ b/extension/interaction/js/scripts/content/content-page-elements.js @@ -1,15 +1,27 @@ -/** - * @file Binds an events to the elements on a page. +/** + * The module that handles page elements. + * + * @module ContentPageElements */ function ContentPageElements() {} +/** + * Selected posts of a thread. + * + * @memberof ContentPageElements + * @static + * @type {Array} + */ ContentPageElements.activePosts = []; /** * Binds an events to the checkboxes. * + * @memberof ContentPageElements + * @static + * * @param {NodeList} checkboxes * A checkboxes for binding. */ @@ -30,6 +42,9 @@ ContentPageElements.addEventsToCheckboxes = function(checkboxes) { * Receives a post of the checkbox and * adds this post to the active posts. * + * @memberof ContentPageElements + * @static + * * @param {HTMLInputElement} checkbox */ ContentPageElements.eventForCheckedCheckbox = function(checkbox) { @@ -42,6 +57,9 @@ ContentPageElements.eventForCheckedCheckbox = function(checkbox) { * Receives a post of the checkbox and * removes this post from the active posts. * + * @memberof ContentPageElements + * @static + * * @param {HTMLInputElement} checkbox */ ContentPageElements.eventForUncheckedCheckbox = function(checkbox) { @@ -57,9 +75,12 @@ ContentPageElements.eventForUncheckedCheckbox = function(checkbox) { /** * Gets a post of the checkbox. * + * @memberof ContentPageElements + * @static + * * @param {HTMLInputElement} checkbox * - * @returns {HTMLElement} - A post (DIV). + * @returns {HTMLElement} A post (DIV). */ ContentPageElements.getPostOfCheckbox = function(checkbox) { return ContentAPI.getParent(checkbox, (element) => { diff --git a/extension/interaction/js/scripts/content/content-screenshot.js b/extension/interaction/js/scripts/content/content-screenshot.js index c0be5bc..b6c61ba 100644 --- a/extension/interaction/js/scripts/content/content-screenshot.js +++ b/extension/interaction/js/scripts/content/content-screenshot.js @@ -1,9 +1,56 @@ +/** + * The module that handles screenshot requests. + * + * @module ContentScreenshot + */ function ContentScreenshot() {} +/** + * Options of a page. + * + * @memberof ContentScreenshot + * @static + * @type {Object} + */ ContentScreenshot.pageOptions = {}; +/** + * Delay for screenshoting. + * + * @memberof ContentScreenshot + * @static + * @type {Number} + */ +ContentScreenshot.screenshotDelay = ( + ContentAPI.userSettings.settings_screenshot.delay || 500 +); + + +/** + * Delay to limit the time a screenshot is taken. + * + * @memberof ContentScreenshot + * @static + * @type {Number} + */ +ContentScreenshot.errorDelay = ContentScreenshot.screenshotDelay * 10; + + +/** + * Coordinate of an element on a page. + * + * @memberof ContentScreenshot + * @static + * @constructor + * + * @param {Number} [scrollX=0] + * @param {Number} [scrollY=0] + * @param {Number} [width=0] + * @param {Number} [height=0] + * @param {Number} [offsetTop=0] + */ ContentScreenshot.Coordinate = function(scrollX, scrollY, width, height, offsetTop) { this.scrollX = scrollX || 0; this.scrollY = scrollY || 0; @@ -13,6 +60,18 @@ ContentScreenshot.Coordinate = function(scrollX, scrollY, width, height, offsetT } +/** + * Sorts coordinates. + * + * @memberof ContentScreenshot + * @static + * + * @param {Array} coordinates + * A coordiantes for sorting. + * + * @param {String} property + * A property for sorting. + */ ContentScreenshot.sortCoordinates = function(coordinates, property) { coordinates.sort((a, b) => { return a[property] - b[property]; @@ -20,28 +79,47 @@ ContentScreenshot.sortCoordinates = function(coordinates, property) { } -ContentScreenshot.createScreenshotOfPosts = function() { +/** + * Creates screenshot of selected posts. + * + * @memberof ContentScreenshot + * @static + * @async + * + * @throws {Error} Throws an error if occurs. + */ +ContentScreenshot.createScreenshotOfPosts = async function() { const thread = ContentAPI.getThread(); this.setPageOptions(thread); this.changePageOptions(thread); const coordinates = this.getScrenshotCoordinates(thread); - const promise = this.handleScreenshotCoordinates(coordinates); - - promise.then(() => { - this.restorePageOptions(thread); - ContentAPI.sendMessageToBackground({ - type: 'command', - command: 'createPostsImage' - }); - }, (error) => { + + try { + await this.handleScreenshotCoordinates(coordinates); + } catch (error) { this.restorePageOptions(thread); throw error; + } + + this.restorePageOptions(thread); + ContentAPI.sendMessageToBackground({ + type: 'command', + command: 'createPostsImage' }); } +/** + * Gets coordinates of selected posts. + * + * @memberof ContentScreenshot + * @static + * + * @returns {Array} + * An array of sorted (by 'offsetTop') coordinates of selected posts. + */ ContentScreenshot.getScrenshotCoordinates = function() { const postCoordinates = []; @@ -64,6 +142,22 @@ ContentScreenshot.getScrenshotCoordinates = function() { } +/** + * Groups and sends to background script the coordinates. + * + * @memberof ContentScreenshot + * @static + * @async + * + * @param {Array} coordinates + * A coordinates for handling. + * + * @returns {Promise} + * A promise for the handle that will resolve when all cordinates + * will be grouped and sended to background script. + * Resolve will contain nothing if success, + * otherwise reject will contain an error. + */ ContentScreenshot.handleScreenshotCoordinates = function(coordinates) { return new Promise((resolve, reject) => { const innerHeight = window.innerHeight; @@ -102,7 +196,7 @@ ContentScreenshot.handleScreenshotCoordinates = function(coordinates) { error.message = 'Timeout limit.'; return rej(error); - }, 10000); + }, this.errorDelay); window.setTimeout(() => { ContentAPI.sendMessageToBackground({ @@ -127,12 +221,13 @@ ContentScreenshot.handleScreenshotCoordinates = function(coordinates) { return rej(); } }); - }, 500); + }, this.screenshotDelay); } }); }); } + // unsent coordinates. promise = promise.then(() => { return new Promise((res, rej) => { if (!visibleCoordinates.length) { @@ -144,7 +239,7 @@ ContentScreenshot.handleScreenshotCoordinates = function(coordinates) { error.message = 'Timeout limit.'; return rej(error); - }, 10000); + }, this.errorDelay); window.setTimeout(() => { ContentAPI.sendMessageToBackground({ @@ -159,47 +254,72 @@ ContentScreenshot.handleScreenshotCoordinates = function(coordinates) { return rej(); } }); - }, 500); + }, this.screenshotDelay); }); }); promise.then(() => { return resolve(); - }, () => { + }, (rejectError) => { const error = new Error(); error.message = 'Failed to create posts screenshot.'; + if (rejectError.message) { + error.message += ' ' + rejectError.message; + } + return reject(error); }); }); } -// This method will work fully, if handle (this.createScreenshotOfPosts()) -// a thread like all selected posts. -// However, this method works faster and consumes less memory. -ContentScreenshot.createScreenshotOfThread = function() { +/* + * This method will work fully, if handle a thread like all selected posts. + * However, this method works faster and consumes less memory. + */ + +/** + * Creates screenshot of thread. + * + * @memberof ContentScreenshot + * @static + * @async + * + * @throws {Error} Throws an error if occurs. + */ +ContentScreenshot.createScreenshotOfThread = async function() { const thread = ContentAPI.getThread(); this.setPageOptions(thread); this.changePageOptions(thread); const coordinates = this.getThreadCoordinates(thread); - const promise = this.handleThreadCoordinates(coordinates); - - promise.then(() => { - this.restorePageOptions(thread); - ContentAPI.sendMessageToBackground({ - type: 'command', - command: 'createThreadImage' - }); - }, (error) => { + + try { + await this.handleThreadCoordinates(coordinates); + } catch (error) { this.restorePageOptions(thread); throw error; + } + + this.restorePageOptions(thread); + ContentAPI.sendMessageToBackground({ + type: 'command', + command: 'createThreadImage' }); } +/** + * Gets coordinates of thread. + * + * @memberof ContentScreenshot + * @static + * + * @returns {Array} + * An array of coordinates of a thread. + */ ContentScreenshot.getThreadCoordinates = function(thread) { const coordinates = []; @@ -258,6 +378,22 @@ ContentScreenshot.getThreadCoordinates = function(thread) { } +/** + * Sends to background script the coordinates. + * + * @memberof ContentScreenshot + * @static + * @async + * + * @param {Array} coordinates + * A coordinates for handling. + * + * @returns {Promise} + * A promise for the handle that will resolve when + * all cordinates will be sended to background script. + * Resolve will contain nothing if success, + * otherwise reject will contain an error. + */ ContentScreenshot.handleThreadCoordinates = function(coordinates) { return new Promise((resolve, reject) => { let promise = Promise.resolve(); @@ -270,7 +406,7 @@ ContentScreenshot.handleThreadCoordinates = function(coordinates) { error.message = 'Timeout limit.'; return rej(error); - }, 10000); + }, this.errorDelay); window.scrollTo( coordinate.scrollX, @@ -291,17 +427,21 @@ ContentScreenshot.handleThreadCoordinates = function(coordinates) { return rej(); } }); - }, 500); + }, this.screenshotDelay); }); }); } promise.then(() => { return resolve(); - }, () => { + }, (rejectError) => { const error = new Error(); error.message = 'Failed to create thread screenshot.'; + if (rejectError.message) { + error.message += ' ' + rejectError.message; + } + return reject(error); }); }); @@ -310,8 +450,12 @@ ContentScreenshot.handleThreadCoordinates = function(coordinates) { /** * Gets a default page options. + * Options will be setted to ContentScreenshot.pageOptions. + * + * @memberof ContentScreenshot + * @static * - * @returns {Object} + * @param {HTMLElement} thread A thread on a page. */ ContentScreenshot.setPageOptions = function(thread) { const upNavArrow = document.getElementById('up-nav-arrow'); @@ -334,8 +478,13 @@ ContentScreenshot.setPageOptions = function(thread) { } -/** - * Changes a page options. + /** + * Changes a page options on the options needed for screenshot. + * + * @memberof ContentScreenshot + * @static + * + * @param {HTMLElement} thread A thread on a page. */ ContentScreenshot.changePageOptions = function(thread) { // Try to make pages with bad scrolling work, e.g., ones with @@ -366,9 +515,13 @@ ContentScreenshot.changePageOptions = function(thread) { /** - * Set the page options. + * Restores default page options. + * Options will be taken from ContentScreenshot.pageOptions. + * + * @memberof ContentScreenshot + * @static * - * @param {Object} options + * @param {HTMLElement} thread A thread on a page. */ ContentScreenshot.restorePageOptions = function(thread) { const options = this.pageOptions; diff --git a/extension/interface/css/libs/bootstrap-slider.min.css b/extension/interface/css/libs/bootstrap-slider.min.css new file mode 100644 index 0000000..1cf68b5 --- /dev/null +++ b/extension/interface/css/libs/bootstrap-slider.min.css @@ -0,0 +1,41 @@ +/*! ======================================================= + VERSION 10.0.0 +========================================================= */ +/*! ========================================================= + * bootstrap-slider.js + * + * Maintainers: + * Kyle Kemp + * - Twitter: @seiyria + * - Github: seiyria + * Rohit Kalkur + * - Twitter: @Rovolutionary + * - Github: rovolution + * + * ========================================================= + * + * bootstrap-slider is released under the MIT License + * Copyright (c) 2017 Kyle Kemp, Rohit Kalkur, and contributors + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * ========================================================= */.slider{display:inline-block;vertical-align:middle;position:relative}.slider.slider-horizontal{width:210px;height:20px}.slider.slider-horizontal .slider-track{height:10px;width:100%;margin-top:-5px;top:50%;left:0}.slider.slider-horizontal .slider-selection,.slider.slider-horizontal .slider-track-low,.slider.slider-horizontal .slider-track-high{height:100%;top:0;bottom:0}.slider.slider-horizontal .slider-tick,.slider.slider-horizontal .slider-handle{margin-left:-10px}.slider.slider-horizontal .slider-tick.triangle,.slider.slider-horizontal .slider-handle.triangle{position:relative;top:50%;-ms-transform:translateY(-50%);transform:translateY(-50%);border-width:0 10px 10px 10px;width:0;height:0;border-bottom-color:#2e6da4;margin-top:0}.slider.slider-horizontal .slider-tick-container{white-space:nowrap;position:absolute;top:0;left:0;width:100%}.slider.slider-horizontal .slider-tick-label-container{white-space:nowrap;margin-top:20px}.slider.slider-horizontal .slider-tick-label-container .slider-tick-label{padding-top:4px;display:inline-block;text-align:center}.slider.slider-horizontal .tooltip{-ms-transform:translateX(-50%);transform:translateX(-50%)}.slider.slider-horizontal.slider-rtl .slider-track{left:initial;right:0}.slider.slider-horizontal.slider-rtl .slider-tick,.slider.slider-horizontal.slider-rtl .slider-handle{margin-left:initial;margin-right:-10px}.slider.slider-horizontal.slider-rtl .slider-tick-container{left:initial;right:0}.slider.slider-horizontal.slider-rtl .tooltip{-ms-transform:translateX(50%);transform:translateX(50%)}.slider.slider-vertical{height:210px;width:20px}.slider.slider-vertical .slider-track{width:10px;height:100%;left:25%;top:0}.slider.slider-vertical .slider-selection{width:100%;left:0;top:0;bottom:0}.slider.slider-vertical .slider-track-low,.slider.slider-vertical .slider-track-high{width:100%;left:0;right:0}.slider.slider-vertical .slider-tick,.slider.slider-vertical .slider-handle{margin-top:-10px}.slider.slider-vertical .slider-tick.triangle,.slider.slider-vertical .slider-handle.triangle{border-width:10px 0 10px 10px;width:1px;height:1px;border-left-color:#2e6da4;border-right-color:#2e6da4;margin-left:0;margin-right:0}.slider.slider-vertical .slider-tick-label-container{white-space:nowrap}.slider.slider-vertical .slider-tick-label-container .slider-tick-label{padding-left:4px}.slider.slider-vertical .tooltip{-ms-transform:translateY(-50%);transform:translateY(-50%)}.slider.slider-vertical.slider-rtl .slider-track{left:initial;right:25%}.slider.slider-vertical.slider-rtl .slider-selection{left:initial;right:0}.slider.slider-vertical.slider-rtl .slider-tick.triangle,.slider.slider-vertical.slider-rtl .slider-handle.triangle{border-width:10px 10px 10px 0}.slider.slider-vertical.slider-rtl .slider-tick-label-container .slider-tick-label{padding-left:initial;padding-right:4px}.slider.slider-disabled .slider-handle{background-image:-webkit-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:-o-linear-gradient(top,#dfdfdf 0,#bebebe 100%);background-image:linear-gradient(to bottom,#dfdfdf 0,#bebebe 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdfdfdf',endColorstr='#ffbebebe',GradientType=0)}.slider.slider-disabled .slider-track{background-image:-webkit-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:-o-linear-gradient(top,#e5e5e5 0,#e9e9e9 100%);background-image:linear-gradient(to bottom,#e5e5e5 0,#e9e9e9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe5e5e5',endColorstr='#ffe9e9e9',GradientType=0);cursor:not-allowed}.slider input{display:none}.slider .tooltip.top{margin-top:-36px}.slider .tooltip-inner{white-space:nowrap;max-width:none}.slider .hide{display:none}.slider-track{position:absolute;cursor:pointer;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#f9f9f9 100%);background-image:linear-gradient(to bottom,#f5f5f5 0,#f9f9f9 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);border-radius:4px}.slider-selection{position:absolute;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-selection.tick-slider-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0)}.slider-track-low,.slider-track-high{position:absolute;background:transparent;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;border-radius:4px}.slider-handle{position:absolute;top:0;width:20px;height:20px;background-color:#337ab7;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7',endColorstr='#ff2e6da4',GradientType=0);filter:none;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.2),0 1px 2px rgba(0,0,0,.05);border:0 solid transparent}.slider-handle.round{border-radius:50%}.slider-handle.triangle{background:transparent none}.slider-handle.custom{background:transparent none}.slider-handle.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick{position:absolute;width:20px;height:20px;background-image:-webkit-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#f9f9f9 0,#f5f5f5 100%);background-image:linear-gradient(to bottom,#f9f9f9 0,#f5f5f5 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9',endColorstr='#fff5f5f5',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;filter:none;opacity:.8;border:0 solid transparent}.slider-tick.round{border-radius:50%}.slider-tick.triangle{background:transparent none}.slider-tick.custom{background:transparent none}.slider-tick.custom::before{line-height:20px;font-size:20px;content:'\2605';color:#726204}.slider-tick.in-selection{background-image:-webkit-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:-o-linear-gradient(top,#8ac1ef 0,#82b3de 100%);background-image:linear-gradient(to bottom,#8ac1ef 0,#82b3de 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff8ac1ef',endColorstr='#ff82b3de',GradientType=0);opacity:1} \ No newline at end of file diff --git a/extension/interface/css/styles/settings-iframe.css b/extension/interface/css/styles/settings-iframe.css new file mode 100644 index 0000000..3e0b961 --- /dev/null +++ b/extension/interface/css/styles/settings-iframe.css @@ -0,0 +1,26 @@ +/* Global. */ + +@font-face { + font-family: 'Roboto'; + src: url(/interface/fonts/Roboto-Regular.ttf); +} + +html { + min-height: 100%; + position: relative; +} + +body { + font-family: 'Roboto'; + background-color: #EEE; +} + +html, +body { + margin: 0; + padding: 0; +} + +.custom-container { + padding-top: 20px; +} \ No newline at end of file diff --git a/extension/interface/css/styles/settings.css b/extension/interface/css/styles/settings.css new file mode 100644 index 0000000..cd42ef6 --- /dev/null +++ b/extension/interface/css/styles/settings.css @@ -0,0 +1,94 @@ +/* Global. */ + +@font-face { + font-family: 'Roboto'; + src: url(/interface/fonts/Roboto-Regular.ttf); +} + +html { + min-height: 100%; + position: relative; +} + +body { + font-family: 'Roboto'; + background-color: #EEE; +} + +html, +body, +h1, +p { + margin: 0; + padding: 0; +} + + +/* Common. */ + +.custom-container { + max-width: 50vw; + width: 100%; + margin-left: auto; + margin-right: auto; +} + + +.custom-col-align-right { + padding-right: 0; + text-align: right; +} + +.custom-col-align-left { + padding-left: 0; + text-align: left; +} + + +/* Header. */ + +.custom-header { + height: 80px; +} + +.custom-header-content { + padding-top: 20px; + text-align: center; +} + + +/* Main. */ + +.custom-main { + padding-bottom: 40px; +} + +.custom-main-content { + padding-top: 20px; + text-align: center; +} + +.custom-main-iframes iframe { + display: none; + width: 100%; +} + +.custom-main-iframes iframe:nth-child(1) { + display: block; + height: 470px; +} + + +/* Footer. */ + +.custom-footer { + position: absolute; + bottom: 0; + height: 40px; + width: 100%; +} + +.custom-footer-content { + line-height: 40px; + text-align: center; +} \ No newline at end of file diff --git a/extension/interface/html/popup.html b/extension/interface/html/popup.html index d026880..01c942f 100644 --- a/extension/interface/html/popup.html +++ b/extension/interface/html/popup.html @@ -6,9 +6,9 @@ - 2ch+ + 2ch-helper - + @@ -81,7 +81,7 @@

diff --git a/extension/interface/html/settings-download.html b/extension/interface/html/settings-download.html new file mode 100644 index 0000000..d7fb1e9 --- /dev/null +++ b/extension/interface/html/settings-download.html @@ -0,0 +1,50 @@ + + + + + + + + Настройки - Загрузка + + + + + + + + + +
+
+
+
+ + +
+ + +
+ +
+ + +
+ + +
+ +
+ + + + мс +
+ + +
+
+
+ + + \ No newline at end of file diff --git a/extension/interface/html/settings-screenshot.html b/extension/interface/html/settings-screenshot.html new file mode 100644 index 0000000..f2626a3 --- /dev/null +++ b/extension/interface/html/settings-screenshot.html @@ -0,0 +1,59 @@ + + + + + + + + Настройки - Скриншот + + + + + + + + + +
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + + % +
+ +
+ + + + мс +
+ + +
+
+
+ + + \ No newline at end of file diff --git a/extension/interface/html/settings.html b/extension/interface/html/settings.html index 93ffeae..e653a73 100644 --- a/extension/interface/html/settings.html +++ b/extension/interface/html/settings.html @@ -1,5 +1,4 @@ - @@ -8,10 +7,48 @@ Настройки + + - Настройки +
+
+

+ 2ch-helper +

+
+ +
+ +
+
+
+
+
+ Скриншот +
+ +
+ Загрузка +
+
+
+ +
+ + +
+
+
+ +
+ +
- + \ No newline at end of file diff --git a/extension/interface/js/libs/bootstrap-slider.min.js b/extension/interface/js/libs/bootstrap-slider.min.js new file mode 100644 index 0000000..2b98449 --- /dev/null +++ b/extension/interface/js/libs/bootstrap-slider.min.js @@ -0,0 +1,5 @@ +/*! ======================================================= + VERSION 10.0.0 +========================================================= */ +"use strict";var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(a){return typeof a}:function(a){return a&&"function"==typeof Symbol&&a.constructor===Symbol&&a!==Symbol.prototype?"symbol":typeof a},windowIsDefined="object"===("undefined"==typeof window?"undefined":_typeof(window));!function(a){if("function"==typeof define&&define.amd)define(["jquery"],a);else if("object"===("undefined"==typeof module?"undefined":_typeof(module))&&module.exports){var b;try{b=require("jquery")}catch(c){b=null}module.exports=a(b)}else window&&(window.Slider=a(window.jQuery))}(function(a){var b="slider",c="bootstrapSlider";windowIsDefined&&!window.console&&(window.console={}),windowIsDefined&&!window.console.log&&(window.console.log=function(){}),windowIsDefined&&!window.console.warn&&(window.console.warn=function(){});var d;return function(a){function b(){}function c(a){function c(b){b.prototype.option||(b.prototype.option=function(b){a.isPlainObject(b)&&(this.options=a.extend(!0,this.options,b))})}function e(b,c){a.fn[b]=function(e){if("string"==typeof e){for(var g=d.call(arguments,1),h=0,i=this.length;i>h;h++){var j=this[h],k=a.data(j,b);if(k)if(a.isFunction(k[e])&&"_"!==e.charAt(0)){var l=k[e].apply(k,g);if(void 0!==l&&l!==k)return l}else f("no such method '"+e+"' for "+b+" instance");else f("cannot call methods on "+b+" prior to initialization; attempted to call '"+e+"'")}return this}var m=this.map(function(){var d=a.data(this,b);return d?(d.option(e),d._init()):(d=new c(this,e),a.data(this,b,d)),a(this)});return!m||m.length>1?m:m[0]}}if(a){var f="undefined"==typeof console?b:function(a){console.error(a)};return a.bridget=function(a,b){c(b),e(a,b)},a.bridget}}var d=Array.prototype.slice;c(a)}(a),function(a){function e(b,c){function d(a,b){var c="data-slider-"+b.replace(/_/g,"-"),d=a.getAttribute(c);try{return JSON.parse(d)}catch(e){return d}}this._state={value:null,enabled:null,offset:null,size:null,percentage:null,inDrag:!1,over:!1},this.ticksCallbackMap={},this.handleCallbackMap={},"string"==typeof b?this.element=document.querySelector(b):b instanceof HTMLElement&&(this.element=b),c=c?c:{};for(var e=Object.keys(this.defaultOptions),f=0;f0)for(var t=0;t0){for(this.ticksContainer=document.createElement("div"),this.ticksContainer.className="slider-tick-container",f=0;f0)for(this.tickLabelContainer=document.createElement("div"),this.tickLabelContainer.className="slider-tick-label-container",f=0;f0&&(this.options.max=Math.max.apply(Math,this.options.ticks),this.options.min=Math.min.apply(Math,this.options.ticks)),Array.isArray(this.options.value)?(this.options.range=!0,this._state.value=this.options.value):this.options.range?this._state.value=[this.options.value,this.options.max]:this._state.value=this.options.value,this.trackLow=k||this.trackLow,this.trackSelection=j||this.trackSelection,this.trackHigh=l||this.trackHigh,"none"===this.options.selection?(this._addClass(this.trackLow,"hide"),this._addClass(this.trackSelection,"hide"),this._addClass(this.trackHigh,"hide")):("after"===this.options.selection||"before"===this.options.selection)&&(this._removeClass(this.trackLow,"hide"),this._removeClass(this.trackSelection,"hide"),this._removeClass(this.trackHigh,"hide")),this.handle1=m||this.handle1,this.handle2=n||this.handle2,p===!0)for(this._removeClass(this.handle1,"round triangle"),this._removeClass(this.handle2,"round triangle hide"),f=0;f0){for(var d,e,f,g=0,h=1;hthis.options.max?this.options.max:k},toPercentage:function(a){if(this.options.max===this.options.min)return 0;if(this.options.ticks_positions.length>0){for(var b,c,d,e=0,f=0;f0?this.options.ticks[f-1]:0,d=f>0?this.options.ticks_positions[f-1]:0,c=this.options.ticks[f],e=this.options.ticks_positions[f];break}if(f>0){var g=(a-b)/(c-b);return d+g*(e-d)}}return 100*(a-this.options.min)/(this.options.max-this.options.min)}},logarithmic:{toValue:function(a){var b=0===this.options.min?0:Math.log(this.options.min),c=Math.log(this.options.max),d=Math.exp(b+(c-b)*a/100);return Math.round(d)===this.options.max?this.options.max:(d=this.options.min+Math.round((d-this.options.min)/this.options.step)*this.options.step,dthis.options.max?this.options.max:d)},toPercentage:function(a){if(this.options.max===this.options.min)return 0;var b=Math.log(this.options.max),c=0===this.options.min?0:Math.log(this.options.min),d=0===a?0:Math.log(a);return 100*(d-c)/(b-c)}}};if(d=function(a,b){return e.call(this,a,b),this},d.prototype={_init:function(){},constructor:d,defaultOptions:{id:"",min:0,max:10,step:1,precision:0,orientation:"horizontal",value:5,range:!1,selection:"before",tooltip:"show",tooltip_split:!1,handle:"round",reversed:!1,rtl:"auto",enabled:!0,formatter:function(a){return Array.isArray(a)?a[0]+" : "+a[1]:a},natural_arrow_keys:!1,ticks:[],ticks_positions:[],ticks_labels:[],ticks_snap_bounds:0,ticks_tooltip:!1,scale:"linear",focus:!1,tooltip_position:null,labelledby:null,rangeHighlights:[]},getElement:function(){return this.sliderElem},getValue:function(){return this.options.range?this._state.value:this._state.value[0]},setValue:function(a,b,c){a||(a=0);var d=this.getValue();this._state.value=this._validateInputValue(a);var e=this._applyPrecision.bind(this);this.options.range?(this._state.value[0]=e(this._state.value[0]),this._state.value[1]=e(this._state.value[1]),this._state.value[0]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[0])),this._state.value[1]=Math.max(this.options.min,Math.min(this.options.max,this._state.value[1]))):(this._state.value=e(this._state.value),this._state.value=[Math.max(this.options.min,Math.min(this.options.max,this._state.value))],this._addClass(this.handle2,"hide"),"after"===this.options.selection?this._state.value[1]=this.options.max:this._state.value[1]=this.options.min),this.options.max>this.options.min?this._state.percentage=[this._toPercentage(this._state.value[0]),this._toPercentage(this._state.value[1]),100*this.options.step/(this.options.max-this.options.min)]:this._state.percentage=[0,0,100],this._layout();var f=this.options.range?this._state.value:this._state.value[0];return this._setDataVal(f),b===!0&&this._trigger("slide",f),d!==f&&c===!0&&this._trigger("change",{oldValue:d,newValue:f}),this},destroy:function(){this._removeSliderEventHandlers(),this.sliderElem.parentNode.removeChild(this.sliderElem),this.element.style.display="",this._cleanUpEventCallbacksMap(),this.element.removeAttribute("data"),a&&(this._unbindJQueryEventHandlers(),this.$element.removeData("slider"))},disable:function(){return this._state.enabled=!1,this.handle1.removeAttribute("tabindex"),this.handle2.removeAttribute("tabindex"),this._addClass(this.sliderElem,"slider-disabled"),this._trigger("slideDisabled"),this},enable:function(){return this._state.enabled=!0,this.handle1.setAttribute("tabindex",0),this.handle2.setAttribute("tabindex",0),this._removeClass(this.sliderElem,"slider-disabled"),this._trigger("slideEnabled"),this},toggle:function(){return this._state.enabled?this.disable():this.enable(),this},isEnabled:function(){return this._state.enabled},on:function(a,b){return this._bindNonQueryEventHandler(a,b),this},off:function(b,c){a?(this.$element.off(b,c),this.$sliderElem.off(b,c)):this._unbindNonQueryEventHandler(b,c)},getAttribute:function(a){return a?this.options[a]:this.options},setAttribute:function(a,b){return this.options[a]=b,this},refresh:function(){return this._removeSliderEventHandlers(),e.call(this,this.element,this.options),a&&a.data(this.element,"slider",this),this},relayout:function(){return this._resize(),this._layout(),this},_removeSliderEventHandlers:function(){if(this.handle1.removeEventListener("keydown",this.handle1Keydown,!1),this.handle2.removeEventListener("keydown",this.handle2Keydown,!1),this.options.ticks_tooltip){for(var a=this.ticksContainer.getElementsByClassName("slider-tick"),b=0;b=0?c:this.attributes["aria-valuenow"].value,e=parseInt(d,10);b.value[0]=e,b.percentage[0]=a.options.ticks_positions[e],a._setToolTipOnMouseOver(b),a._showTooltip()};return b.addEventListener("mouseenter",d,!1),d},addMouseLeave:function(a,b){var c=function(){a._hideTooltip()};return b.addEventListener("mouseleave",c,!1),c}}},_layout:function(){var a;if(a=this.options.reversed?[100-this._state.percentage[0],this.options.range?100-this._state.percentage[1]:this._state.percentage[1]]:[this._state.percentage[0],this._state.percentage[1]],this.handle1.style[this.stylePos]=a[0]+"%",this.handle1.setAttribute("aria-valuenow",this._state.value[0]),isNaN(this.options.formatter(this._state.value[0]))&&this.handle1.setAttribute("aria-valuetext",this.options.formatter(this._state.value[0])),this.handle2.style[this.stylePos]=a[1]+"%",this.handle2.setAttribute("aria-valuenow",this._state.value[1]),isNaN(this.options.formatter(this._state.value[1]))&&this.handle2.setAttribute("aria-valuetext",this.options.formatter(this._state.value[1])),this.rangeHighlightElements.length>0&&Array.isArray(this.options.rangeHighlights)&&this.options.rangeHighlights.length>0)for(var b=0;b0){var g,h="vertical"===this.options.orientation?"height":"width";g="vertical"===this.options.orientation?"marginTop":this.options.rtl?"marginRight":"marginLeft";var i=this._state.size/(this.options.ticks.length-1);if(this.tickLabelContainer){var j=0;if(0===this.options.ticks_positions.length)"vertical"!==this.options.orientation&&(this.tickLabelContainer.style[g]=-i/2+"px"),j=this.tickLabelContainer.offsetHeight;else for(k=0;kj&&(j=this.tickLabelContainer.childNodes[k].offsetHeight);"horizontal"===this.options.orientation&&(this.sliderElem.style.marginBottom=j+"px")}for(var k=0;k=a[0]&&l<=a[1]&&this._addClass(this.ticks[k],"in-selection"):"after"===this.options.selection&&l>=a[0]?this._addClass(this.ticks[k],"in-selection"):"before"===this.options.selection&&l<=a[0]&&this._addClass(this.ticks[k],"in-selection"),this.tickLabels[k]&&(this.tickLabels[k].style[h]=i+"px","vertical"!==this.options.orientation&&void 0!==this.options.ticks_positions[k]?(this.tickLabels[k].style.position="absolute",this.tickLabels[k].style[this.stylePos]=l+"%",this.tickLabels[k].style[g]=-i/2+"px"):"vertical"===this.options.orientation&&(this.options.rtl?this.tickLabels[k].style.marginRight=this.sliderElem.offsetWidth+"px":this.tickLabels[k].style.marginLeft=this.sliderElem.offsetWidth+"px",this.tickLabelContainer.style[g]=this.sliderElem.offsetWidth/2*-1+"px"))}}var m;if(this.options.range){m=this.options.formatter(this._state.value),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=(a[1]+a[0])/2+"%";var n=this.options.formatter(this._state.value[0]);this._setText(this.tooltipInner_min,n);var o=this.options.formatter(this._state.value[1]);this._setText(this.tooltipInner_max,o),this.tooltip_min.style[this.stylePos]=a[0]+"%",this.tooltip_max.style[this.stylePos]=a[1]+"%"}else m=this.options.formatter(this._state.value[0]),this._setText(this.tooltipInner,m),this.tooltip.style[this.stylePos]=a[0]+"%";if("vertical"===this.options.orientation)this.trackLow.style.top="0",this.trackLow.style.height=Math.min(a[0],a[1])+"%",this.trackSelection.style.top=Math.min(a[0],a[1])+"%",this.trackSelection.style.height=Math.abs(a[0]-a[1])+"%",this.trackHigh.style.bottom="0",this.trackHigh.style.height=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";else{"right"===this.stylePos?this.trackLow.style.right="0":this.trackLow.style.left="0",this.trackLow.style.width=Math.min(a[0],a[1])+"%","right"===this.stylePos?this.trackSelection.style.right=Math.min(a[0],a[1])+"%":this.trackSelection.style.left=Math.min(a[0],a[1])+"%",this.trackSelection.style.width=Math.abs(a[0]-a[1])+"%","right"===this.stylePos?this.trackHigh.style.left="0":this.trackHigh.style.right="0",this.trackHigh.style.width=100-Math.min(a[0],a[1])-Math.abs(a[0]-a[1])+"%";var p=this.tooltip_min.getBoundingClientRect(),q=this.tooltip_max.getBoundingClientRect();"bottom"===this.options.tooltip_position?p.right>q.left?(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top="",this.tooltip_max.style.bottom="22px"):(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top=this.tooltip_min.style.top,this.tooltip_max.style.bottom=""):p.right>q.left?(this._removeClass(this.tooltip_max,"top"),this._addClass(this.tooltip_max,"bottom"),this.tooltip_max.style.top="18px"):(this._removeClass(this.tooltip_max,"bottom"),this._addClass(this.tooltip_max,"top"),this.tooltip_max.style.top=this.tooltip_min.style.top)}},_createHighlightRange:function(a,b){return this._isHighlightRange(a,b)?a>b?{start:b,size:a-b}:{start:a,size:b-a}:null},_isHighlightRange:function(a,b){return a>=0&&100>=a&&b>=0&&100>=b?!0:!1},_resize:function(a){this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos],this._layout()},_removeProperty:function(a,b){a.style.removeProperty?a.style.removeProperty(b):a.style.removeAttribute(b)},_mousedown:function(a){if(!this._state.enabled)return!1;this._state.offset=this._offset(this.sliderElem),this._state.size=this.sliderElem[this.sizePos];var b=this._getPercentage(a);if(this.options.range){var c=Math.abs(this._state.percentage[0]-b),d=Math.abs(this._state.percentage[1]-b);this._state.dragged=d>c?0:1,this._adjustPercentageForRangeSliders(b)}else this._state.dragged=0;this._state.percentage[this._state.dragged]=b,this._layout(),this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),this.mousemove&&document.removeEventListener("mousemove",this.mousemove,!1),this.mouseup&&document.removeEventListener("mouseup",this.mouseup,!1),this.mousemove=this._mousemove.bind(this),this.mouseup=this._mouseup.bind(this),this.touchCapable&&(document.addEventListener("touchmove",this.mousemove,!1),document.addEventListener("touchend",this.mouseup,!1)),document.addEventListener("mousemove",this.mousemove,!1),document.addEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!0;var e=this._calculateValue();return this._trigger("slideStart",e),this._setDataVal(e),this.setValue(e,!1,!0),a.returnValue=!1,this.options.focus&&this._triggerFocusOnHandle(this._state.dragged),!0},_touchstart:function(a){if(void 0===a.changedTouches)return void this._mousedown(a);var b=a.changedTouches[0];this.touchX=b.pageX,this.touchY=b.pageY},_triggerFocusOnHandle:function(a){0===a&&this.handle1.focus(),1===a&&this.handle2.focus()},_keydown:function(a,b){if(!this._state.enabled)return!1;var c;switch(b.keyCode){case 37:case 40:c=-1;break;case 39:case 38:c=1}if(c){if(this.options.natural_arrow_keys){var d="vertical"===this.options.orientation&&!this.options.reversed,e="horizontal"===this.options.orientation&&this.options.reversed;(d||e)&&(c=-c)}var f=this._state.value[a]+c*this.options.step,g=f/this.options.max*100;if(this._state.keyCtrl=a,this.options.range){this._adjustPercentageForRangeSliders(g);var h=this._state.keyCtrl?this._state.value[0]:f,i=this._state.keyCtrl?f:this._state.value[1];f=[h,i]}return this._trigger("slideStart",f),this._setDataVal(f),this.setValue(f,!0,!0),this._setDataVal(f),this._trigger("slideStop",f),this._layout(),this._pauseEvent(b),delete this._state.keyCtrl,!1}},_pauseEvent:function(a){a.stopPropagation&&a.stopPropagation(),a.preventDefault&&a.preventDefault(),a.cancelBubble=!0,a.returnValue=!1},_mousemove:function(a){if(!this._state.enabled)return!1;var b=this._getPercentage(a);this._adjustPercentageForRangeSliders(b),this._state.percentage[this._state.dragged]=b,this._layout();var c=this._calculateValue(!0);return this.setValue(c,!0,!0),!1},_touchmove:function(a){if(void 0!==a.changedTouches){var b=a.changedTouches[0],c=b.pageX-this.touchX,d=b.pageY-this.touchY;this._state.inDrag||("vertical"===this.options.orientation&&5>=c&&c>=-5&&(d>=15||-15>=d)?this._mousedown(a):5>=d&&d>=-5&&(c>=15||-15>=c)&&this._mousedown(a))}},_adjustPercentageForRangeSliders:function(a){if(this.options.range){var b=this._getNumDigitsAfterDecimalPlace(a);b=b?b-1:0;var c=this._applyToFixedAndParseFloat(a,b);0===this._state.dragged&&this._applyToFixedAndParseFloat(this._state.percentage[1],b)c?(this._state.percentage[1]=this._state.percentage[0],this._state.dragged=0):0===this._state.keyCtrl&&this._state.value[1]/this.options.max*100a&&(this._state.percentage[1]=this._state.percentage[0],this._state.keyCtrl=0,this.handle1.focus())}},_mouseup:function(){if(!this._state.enabled)return!1;this.touchCapable&&(document.removeEventListener("touchmove",this.mousemove,!1),document.removeEventListener("touchend",this.mouseup,!1)),document.removeEventListener("mousemove",this.mousemove,!1),document.removeEventListener("mouseup",this.mouseup,!1),this._state.inDrag=!1,this._state.over===!1&&this._hideTooltip();var a=this._calculateValue(!0);return this._layout(),this._setDataVal(a),this._trigger("slideStop",a),!1},_calculateValue:function(a){var b;if(this.options.range?(b=[this.options.min,this.options.max],0!==this._state.percentage[0]&&(b[0]=this._toValue(this._state.percentage[0]),b[0]=this._applyPrecision(b[0])),100!==this._state.percentage[1]&&(b[1]=this._toValue(this._state.percentage[1]),b[1]=this._applyPrecision(b[1]))):(b=this._toValue(this._state.percentage[0]),b=parseFloat(b),b=this._applyPrecision(b)),a){for(var c=[b,1/0],d=0;d { + name.style.display = 'none'; + }); + + user.addEventListener('click', () => { + name.style.display = 'block'; + }); +} + + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', SettingsDownload.main); +} else { + SettingsDownload.main(); +} diff --git a/extension/interface/js/scripts/settings-iframe.js b/extension/interface/js/scripts/settings-iframe.js new file mode 100644 index 0000000..2e67450 --- /dev/null +++ b/extension/interface/js/scripts/settings-iframe.js @@ -0,0 +1,88 @@ +function SettingsIframe() {} + + +SettingsIframe.userSettings = {}; + + +SettingsIframe.userSettingIds = [ + 'settings_screenshot', + 'settings_download' +]; + + +SettingsIframe.initUserSettings = function() { + return new Promise((resolve, reject) => { + chrome.storage.sync.get(this.userSettingId, (data) => { + this.userSettings = data; + return resolve(); + }); + }); +} + + +SettingsIframe.saveUserSettings = function(forms, settingField) { + this.updateUserData(forms, settingField); + chrome.storage.sync.set(this.userSettings); +} + + +SettingsIframe.updateUserData = function(forms, settingField) { + for (let formData of forms) { + for (let data of formData.data) { + const element = document.getElementById(data.elementId); + let value = undefined; + + if (formData.type === 'input' || formData.type === 'select') { + value = element.value; + } else if (formData.type === 'span') { + value = Number(element.textContent); + } else if (formData.type === 'checkbox') { + value = element.checked; + } + + this.userSettings[settingField][data.settingId] = value; + } + } +} + + +SettingsIframe.bindButtons = function(buttons) { + for (let buttonData of buttons) { + const element = document.getElementById(buttonData.id); + + for (let event of buttonData.events) { + element.addEventListener(event.type, event.event); + } + } +} + + +SettingsIframe.bindSliders = function(sliders, settingField) { + for (let sliderData of sliders) { + const options = sliderData.options; + options['value'] = this.userSettings[settingField][sliderData.settingId]; + const slider = new Slider(sliderData.id, options); + + for (let event of sliderData.events) { + slider.on(event.name, event.event); + } + } +} + + +SettingsIframe.bindForms = function(forms, settingField) { + for (let formData of forms) { + for (let data of formData.data) { + const element = document.getElementById(data.elementId); + const value = this.userSettings[settingField][data.settingId]; + + if (formData.type === 'input' || formData.type === 'select') { + element.value = value; + } else if (formData.type === 'span') { + element.textContent = value; + } else if (formData.type === 'checkbox') { + element.checked = value; + } + } + } +} diff --git a/extension/interface/js/scripts/settings-screenshot.js b/extension/interface/js/scripts/settings-screenshot.js new file mode 100644 index 0000000..0bd26f0 --- /dev/null +++ b/extension/interface/js/scripts/settings-screenshot.js @@ -0,0 +1,127 @@ +function SettingsScreenshot() {} + + +SettingsScreenshot.userSettingId = 'settings_screenshot'; + + +SettingsScreenshot.buttons = [ + { + id: 'save', + events: [ + { + type: 'click', + event: function() { + SettingsScreenshot.buttonClick(); + } + } + ] + } +]; + + +SettingsScreenshot.sliders = [ + { + id: '#screenshotQuality', + settingId: 'quality', + options: { + tooltip: 'hide' + }, + events: [ + { + name: 'change', + event: function(sliderValue) { + const id = 'screenshotQualityValue'; + const value = sliderValue.newValue; + + SettingsScreenshot.sliderChange(id, value); + } + } + ] + }, + { + id: '#screenshotDelay', + settingId: 'delay', + options: { + tooltip: 'hide' + }, + events: [ + { + name: 'change', + event: function(sliderValue) { + const id = 'screenshotDelayValue'; + const value = sliderValue.newValue; + + SettingsScreenshot.sliderChange(id, value); + } + } + ] + } +]; + + +SettingsScreenshot.forms = [ + { + type: 'input', + data: [ + {elementId: 'nameForPosts', settingId: 'fileNamePosts'}, + {elementId: 'nameForThread', settingId: 'fileNameThread'} + ] + }, + { + type: 'select', + data: [ + {elementId: 'screenshotFormat', settingId: 'format'} + ] + }, + { + type: 'span', + data: [ + {elementId: 'screenshotQualityValue', settingId: 'quality'}, + {elementId: 'screenshotDelayValue', settingId: 'delay'} + ] + } +]; + + +SettingsScreenshot.main = async function() { + await SettingsIframe.initUserSettings(); + SettingsIframe.bindButtons(SettingsScreenshot.buttons); + SettingsIframe.bindSliders(SettingsScreenshot.sliders, SettingsScreenshot.userSettingId); + SettingsIframe.bindForms(SettingsScreenshot.forms, SettingsScreenshot.userSettingId); + SettingsScreenshot.customBind(); +} + + +SettingsScreenshot.buttonClick = function() { + SettingsIframe.saveUserSettings(SettingsScreenshot.forms, SettingsScreenshot.userSettingId); +} + + +SettingsScreenshot.sliderChange = function(id, value) { + document.getElementById(id).textContent = value; +} + + +SettingsScreenshot.customBind = function() { + const qualityElement = document.getElementById('quality'); + const selectElement = document.getElementById('screenshotFormat'); + + if (selectElement.value === 'png') { + qualityElement.style.display = 'none'; + } + + selectElement.addEventListener('change', (event) => { + if (event.target.value === 'jpeg') { + qualityElement.style.display = 'block'; + } else if (event.target.value === 'png') { + qualityElement.style.display = 'none'; + } + }); +} + + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', SettingsScreenshot.main); +} else { + SettingsScreenshot.main(); +} diff --git a/extension/interface/js/scripts/settings.js b/extension/interface/js/scripts/settings.js new file mode 100644 index 0000000..71b8625 --- /dev/null +++ b/extension/interface/js/scripts/settings.js @@ -0,0 +1,49 @@ +function Settings() {} + + +Settings.iframeData = [ + { + navbarId: 'settings-screenshot-navbar', + elementId: 'settings-screenshot', + height: '470px' + }, + { + navbarId: 'settings-download-navbar', + elementId: 'settings-download', + height: '250px' + } +]; + + +Settings.main = function() { + Settings.bindElements(); +} + + +Settings.bindElements = function() { + for (let element of Settings.iframeData) { + document.getElementById(element.navbarId).onclick = function() { + Settings.hideAllIframes(); + + const iframe = document.getElementById(element.elementId); + + iframe.style.display = 'block'; + iframe.style.height = element.height; + } + } +} + +Settings.hideAllIframes = function() { + const iframes = document.querySelectorAll('iframe'); + + for (let iframe of iframes) { + iframe.style.display = 'none'; + } +} + + +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', Settings.main); +} else { + Settings.main(); +} diff --git a/extension/manifest.json b/extension/manifest.json index f3066a1..88de2c2 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,28 +1,41 @@ { "manifest_version": 2, - "name": "2ch+", - "short_name": "2ch+", + + "name": "2ch-helper", + "short_name": "2ch-helper", "version": "1.0.0", - "version_name": "beta-11.02.18", - "description": "Simplifies the interaction with 2ch.hk.", + "version_name": "1.0.0-beta.3", + "description": "Облегчение взаимодействия с имиджбордой 2ch.hk.", + + "author": "Sergey Kuznetsov", + "homepage_url": "https://github.com/Amaimersion/2ch-helper", + + "options_page": "/interface/html/settings.html", + "icons": { "16": "/interface/icons/icon-16.png", "32": "/interface/icons/icon-32.png", "48": "/interface/icons/icon-48.png", "128": "/interface/icons/icon-128.png" }, + "browser_action": { - "default_title": "2ch+", + "default_title": "2ch-helper", "default_popup": "/interface/html/popup.html" }, + "background": { "scripts": [ + "/interaction/js/scripts/background/background-API.js", "/interaction/js/scripts/background/background-screenshot.js", "/interaction/js/scripts/background/background-downloads.js", "/interaction/js/scripts/background/background-on-message.js", - "/interaction/js/scripts/background/background-API.js" - ] + "/interaction/js/scripts/background/background-user-profile.js", + "/interaction/js/scripts/background/background-events.js" + ], + "persistent": false }, + "content_scripts": [ { "matches": [ @@ -36,12 +49,12 @@ ] } ], + "permissions": [ + "storage", "pageCapture", "downloads", "activeTab", "*://2ch.hk/*" - ], - "author": "Sergey Kuznetsov", - "homepage_url": "https://github.com/Amaimersion/2ch-helper" -} \ No newline at end of file + ] +}