diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a76c68b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/0.0.1.zip \ No newline at end of file diff --git a/_locales/en/messages.json b/_locales/en/messages.json new file mode 100644 index 0000000..42b38f7 --- /dev/null +++ b/_locales/en/messages.json @@ -0,0 +1,23 @@ +{ + "openInPopupWindow": { + "message": "Open in popup window" + }, + "extensionDescription": { + "message": "Adds context menu entry to open link in popup window" + }, + "popupHeight": { + "message": "Popup height (px)" + }, + "closeWhenFocusedInitialWindow": { + "message": "Close popup when origin window is focused" + }, + "popupWidth": { + "message": "Popup width (px)" + }, + "tryOpenAtMousePosition": { + "message": "Try to open at mouse position (disabled — open at screen center)" + }, + "hideBrowserControls": { + "message": "Hide browser controls (addressbar, tabbar, etc)" + } +} \ No newline at end of file diff --git a/_locales/ru/messages.json b/_locales/ru/messages.json new file mode 100644 index 0000000..6995f38 --- /dev/null +++ b/_locales/ru/messages.json @@ -0,0 +1,23 @@ +{ + "extensionDescription": { + "message": "Добавляет опцию в контекстное меню для ссылки для открытия её во вплывающем окне" + }, + "closeWhenFocusedInitialWindow": { + "message": "Закрыть всплывающее окно, когда изначальное окно получает фокус" + }, + "openInPopupWindow": { + "message": "Открыть во всплывающем окне" + }, + "popupHeight": { + "message": "Высота окна (px)" + }, + "popupWidth": { + "message": "Ширина окна (px)" + }, + "tryOpenAtMousePosition": { + "message": "Пытаться открыть под курсором мыши (отключено — октрывать в центре экрана)" + }, + "hideBrowserControls": { + "message": "Прятать элементы управления браузера (панель адреса, вкладок, и тд)" + } +} \ No newline at end of file diff --git a/background.js b/background.js new file mode 100644 index 0000000..41c8e4a --- /dev/null +++ b/background.js @@ -0,0 +1,93 @@ +let lastClientX, lastClientY, clientHeight, clientWidth, originWindowId; + +chrome.runtime.onMessage.addListener( + function (request, sender, sendResponse) { + lastClientX = request.lastClientX; + lastClientY = request.lastClientY; + clientHeight = request.clientHeight; + clientWidth = request.clientWidth; + } +); + +const contextMenuItem = { + "id": "openInPopupWindow", + "title": chrome.i18n.getMessage('openInPopupWindow'), + "contexts": ["link"] +}; + +chrome.contextMenus.create(contextMenuItem); + +chrome.contextMenus.onClicked.addListener(function(clickData) { + /// load configs + loadUserConfigs(function(){ + let originalWindowIsFullscreen = false; + + /// store current windowId + chrome.windows.getCurrent( + function(originWindow){ + if (originWindow.type !== 'popup') originWindowId = originWindow.id; + + /// if original window is fullscreen, unmaximize it (for MacOS) + if (originWindow.state == 'fullscreen') { + originalWindowIsFullscreen = true; + chrome.windows.update(originWindow.id, { + 'state': 'maximized' + }); + } + }); + + let dx, dy, height, width; + + // height = window.screen.height * 0.65, width = window.screen.height * 0.5; + height = configs.popupHeight ?? 800, width = configs.popupWidth ?? 600; + height = parseInt(height); width = parseInt(width); + + if (configs.tryOpenAtMousePosition == true && (lastClientX && lastClientY)) { + /// open at last known mouse position + dx = lastClientX - (width / 2), dy = lastClientY - (height / 2); + } else { + /// open at center of screen + dx = (window.screen.width / 2) - (width / 2), dy = (window.screen.height / 2) - (height / 2); + } + // } + + + /// check for screen overflow + if (dx < 0) dx = 0; + if (dy < 0) dy = 0; + if (dx + width > window.screen.width) dx = dx - (dx + width - window.screen.width); + if (dy + height > window.screen.height) dy = dy - (dy + height - window.screen.height); + dx = Math.round(dx); dy = Math.round(dy); + + /// create popup window + setTimeout(function () { + chrome.windows.create({ + 'url': clickData.linkUrl, 'type': configs.hideBrowserControls ? 'popup' : 'normal', 'width': width, 'height': height, + 'top': dy, 'left': dx + }, function (popupWindow) { + /// set coordinates again (workaround for old firefox bug) + chrome.windows.update(popupWindow.id, { + 'top': dy, 'left': dx + }); + + if (configs.closeWhenFocusedInitialWindow == false) return; + + /// close popup on click parent window + function windowFocusListener(windowId) { + if (windowId == originWindowId) { + chrome.windows.onFocusChanged.removeListener(windowFocusListener); + chrome.windows.remove(popupWindow.id); + + if (originalWindowIsFullscreen) chrome.windows.update(parentWindow.id, { + 'state': 'fullscreen' + }); + } + } + + setTimeout(function () { + chrome.windows.onFocusChanged.addListener(windowFocusListener); + }, 300); + }); + }, originalWindowIsFullscreen ? 600 : 0) + }); + }); \ No newline at end of file diff --git a/configs.js b/configs.js new file mode 100644 index 0000000..4529bb7 --- /dev/null +++ b/configs.js @@ -0,0 +1,29 @@ +const configs = { + 'closeWhenFocusedInitialWindow': true, + 'tryOpenAtMousePosition': true, + 'hideBrowserControls': true, + 'popupHeight': 800, + 'popupWidth': 600, +} + +function loadUserConfigs(callback) { + const keys = Object.keys(configs); + chrome.storage.sync.get( + keys, function (userConfigs) { + const l = keys.length; + for (let i = 0; i < l; i++) { + let key = keys[i]; + + if (userConfigs[key] !== null && userConfigs[key] !== undefined) + configs[key] = userConfigs[key]; + + } + + if (callback) callback(userConfigs); + } + ); +} + +function saveAllSettings() { + chrome.storage.sync.set(configs); +} \ No newline at end of file diff --git a/content.js b/content.js new file mode 100644 index 0000000..550f713 --- /dev/null +++ b/content.js @@ -0,0 +1,4 @@ +document.addEventListener("contextmenu", function (e) { + const el = document.elementFromPoint(e.clientX, e.clientY); + chrome.runtime.sendMessage({ lastClientX: e.clientX, lastClientY: e.clientY, clientHeight: el.clientHeight, clientWidth: el.clientWidth}); +}); \ No newline at end of file diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..b24406b --- /dev/null +++ b/icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..c801757 --- /dev/null +++ b/manifest.json @@ -0,0 +1,42 @@ +{ + "manifest_version": 2, + "name": "Open in Popup window", + "description": "__MSG_extensionDescription__", + "default_locale": "en", + "version": "0.0.1", + "icons": { + "48": "icon.svg", + "96": "icon.svg", + "128": "icon.svg" + }, + "background": { + "scripts": [ + "background.js", + "configs.js" + ], + "persistent": false + }, + "content_scripts": [ + { + "matches": [ + "" + ], + "js": [ + "content.js" + ], + "run_at": "document_start" + } + ], + "permissions": [ + "contextMenus", + "storage" + ], + "options_ui": { + "page": "options/options.html" + }, + "browser_specific_settings": { + "gecko": { + "id": "open_in_popup_window@emvaized.dev" + } + } +} \ No newline at end of file diff --git a/options/options.css b/options/options.css new file mode 100644 index 0000000..9f8889f --- /dev/null +++ b/options/options.css @@ -0,0 +1,10 @@ +.option { + padding: 5px; + cursor: pointer; +} + +.option:hover { + background-color: lightgrey; + transition: background-color 50ms ease-in-out; + border-radius: 4px; +} \ No newline at end of file diff --git a/options/options.html b/options/options.html new file mode 100644 index 0000000..ccfa216 --- /dev/null +++ b/options/options.html @@ -0,0 +1,42 @@ + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ + + + + + \ No newline at end of file diff --git a/options/options.js b/options/options.js new file mode 100644 index 0000000..d61747d --- /dev/null +++ b/options/options.js @@ -0,0 +1,42 @@ +document.addEventListener("DOMContentLoaded", init); + +function init(){ + loadUserConfigs(function(userConfigs){ + + const keys = Object.keys(configs); + + for (let i = 0, l = keys.length; i < l; i++) { + const key = keys[i]; + + /// set corresponing input value + let input = document.getElementById(key.toString()); + + /// Set input value + if (input !== null && input !== undefined) { + if (input.type == 'checkbox') { + if ((userConfigs[key] !== null && userConfigs[key] == true) || (userConfigs[key] == null && configs[key] == true)) + input.setAttribute('checked', 0); + else input.removeAttribute('checked', 0); + } else { + input.setAttribute('value', userConfigs[key] ?? configs[key]); + } + + /// Set translated label for input + if (!input.parentNode.innerHTML.includes(chrome.i18n.getMessage(key))) { + input.parentNode.innerHTML += ' ' + chrome.i18n.getMessage(key); + } + + input = document.querySelector('#' + key.toString()); + + /// Set event listener + input.addEventListener("input", function (e) { + let id = input.getAttribute('id'); + let inputValue = input.getAttribute('type') == 'checkbox' ? input.checked : input.value; + configs[id] = inputValue; + + saveAllSettings(); + }); + } + } + }); +} \ No newline at end of file