From 0a608e3de795bb53c6b866d308ef320aecdcb4f4 Mon Sep 17 00:00:00 2001 From: brook hong Date: Thu, 3 Feb 2022 10:08:09 +0800 Subject: [PATCH] Fetch favicon for each search engine in Omnibar, and add switch for search alias in basic settings. --- README.md | 4 +- config/webpack.config.js | 1 + src/background/start.js | 16 +++++++ src/content_scripts/common/default.js | 6 +-- src/content_scripts/content.js | 16 +++++-- src/content_scripts/front.js | 44 ++++++++++---------- src/content_scripts/options.js | 60 +++++++++++++++++++++++++-- src/content_scripts/ui/frontend.css | 21 ++++++++-- src/content_scripts/ui/frontend.js | 17 ++++---- src/content_scripts/ui/omnibar.js | 47 ++++++++++++++++++--- src/manifest.json | 1 - src/pages/options.css | 13 +++++- src/pages/options.html | 9 +++- 13 files changed, 202 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index bceff6209..50db573b4 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ [![Node CI](https://github.com/brookhong/Surfingkeys/workflows/Node%20CI/badge.svg?branch=master)](https://github.com/brookhong/Surfingkeys/actions?query=workflow%3A%22Node+CI%22+branch%3Amaster) -Surfingkeys is another web browser(including Google Chrome, Chromium based browsers, Firefox, Safari) extension that provides keyboard-based navigation and control of the web in the spirit of the VIM editor. But it's not for VIM users only, it's for anyone who just needs some more shortcuts to his own functions. +Surfingkeys is another web browser(including Google Chrome, Chromium based browsers, Firefox, Safari) extension that provides keyboard-based navigation and control of the web in the spirit of the VIM editor. But it's not for VIM users only, it's for anyone who just needs some more shortcuts to his/her own functions. -Surfingkeys is created with all settings described in Javascript, so it's easy for anyone to map any keystrokes to his own defined Javascript function. For example, +Surfingkeys is created with all settings described in Javascript, so it's easy for anyone to map any keystrokes to his/her own defined Javascript function. For example, mapkey('', 'Show me the money', function() { Front.showPopup('a well-known phrase uttered by characters in the 1996 film Jerry Maguire (Escape to close).'); diff --git a/config/webpack.config.js b/config/webpack.config.js index 158e97c36..f32145d27 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -20,6 +20,7 @@ function modifyManifest(browser, mode, buffer) { manifest.permissions.push("cookies", "contextualIdentities"); } else { + manifest.permissions.push("proxy"); manifest.permissions.push("tts"); manifest.permissions.push("downloads.shelf"); manifest.background.persistent = false; diff --git a/src/background/start.js b/src/background/start.js index d769eb9db..77e36d009 100644 --- a/src/background/start.js +++ b/src/background/start.js @@ -1160,6 +1160,22 @@ function start(browser) { }); }, message.headers, message.data); }; + self.requestImage = function(message, sender, sendResponse) { + const img = document.createElement("img"); + img.src = message.url; + img.onload = () => { + const canvas = document.createElement('canvas'); + canvas.height = img.naturalHeight; + canvas.width = img.naturalWidth; + const ctx = canvas.getContext('2d'); + + ctx.drawImage(img, 0, 0, canvas.width, canvas.height); + _response(message, sendResponse, { + text: canvas.toDataURL() + }); + }; + + }; self.nextFrame = function(message, sender, sendResponse) { var tid = sender.tab.id; chrome.tabs.executeScript(tid, { diff --git a/src/content_scripts/common/default.js b/src/content_scripts/common/default.js index b6f0d43a5..9a527a943 100644 --- a/src/content_scripts/common/default.js +++ b/src/content_scripts/common/default.js @@ -349,18 +349,18 @@ module.exports = function(api) { return r.phrase; }); }); - addSearchAlias('b', 'baidu', 'https://www.baidu.com/s?wd=', 's', 'http://suggestion.baidu.com/su?cb=&wd=', function(response) { + addSearchAlias('b', 'baidu', 'https://www.baidu.com/s?wd=', 's', 'https://suggestion.baidu.com/su?cb=&wd=', function(response) { var res = response.text.match(/,s:\[("[^\]]+")]}/); return res ? res[1].replace(/"/g, '').split(",") : []; }); addSearchAlias('e', 'wikipedia', 'https://en.wikipedia.org/wiki/', 's', 'https://en.wikipedia.org/w/api.php?action=opensearch&format=json&formatversion=2&namespace=0&limit=40&search=', function(response) { return JSON.parse(response.text)[1]; }); - addSearchAlias('w', 'bing', 'http://global.bing.com/search?setmkt=en-us&setlang=en-us&q=', 's', 'http://api.bing.com/osjson.aspx?query=', function(response) { + addSearchAlias('w', 'bing', 'https://www.bing.com/search?setmkt=en-us&setlang=en-us&q=', 's', 'https://api.bing.com/osjson.aspx?query=', function(response) { var res = JSON.parse(response.text); return res[1]; }); - addSearchAlias('s', 'stackoverflow', 'http://stackoverflow.com/search?q='); + addSearchAlias('s', 'stackoverflow', 'https://stackoverflow.com/search?q='); addSearchAlias('h', 'github', 'https://github.com/search?q=', 's', 'https://api.github.com/search/repositories?order=desc&q=', function(response) { var res = JSON.parse(response.text)['items']; return res ? res.map(function(r){ diff --git a/src/content_scripts/content.js b/src/content_scripts/content.js index 16a2b9e93..83c3261d8 100644 --- a/src/content_scripts/content.js +++ b/src/content_scripts/content.js @@ -73,8 +73,15 @@ function applySettings(api, normal, rs) { if ('findHistory' in rs) { runtime.conf.lastQuery = rs.findHistory.length ? rs.findHistory[0] : ""; } - if (!rs.showAdvanced && rs.basicMappings) { - applyBasicMappings(api, normal, rs.basicMappings); + if (!rs.showAdvanced) { + if (rs.basicMappings) { + applyBasicMappings(api, normal, rs.basicMappings); + } + if (rs.disabledSearchAliases) { + for (const key in rs.disabledSearchAliases) { + api.removeSearchAlias(key); + } + } } if (rs.showAdvanced && 'snippets' in rs && rs.snippets && !isInUIFrame()) { var delta = runScript(api, rs.snippets); @@ -144,12 +151,15 @@ function _initModules() { const api = createAPI(clipboard, insert, normal, hints, visual, front, _browser); createDefaultMappings(api); + if (typeof(_browser.plugin) === "function") { + _browser.plugin({ front }); + } dispatchSKEvent('defaultSettingsLoaded', {normal, api}); RUNTIME('getSettings', null, function(response) { var rs = response.settings; applySettings(api, normal, rs); - dispatchSKEvent('userSettingsLoaded', {settings: rs, api}); + dispatchSKEvent('userSettingsLoaded', {settings: rs, api, front}); }); return { normal, diff --git a/src/content_scripts/front.js b/src/content_scripts/front.js index efe851d26..de9726cae 100644 --- a/src/content_scripts/front.js +++ b/src/content_scripts/front.js @@ -22,7 +22,7 @@ function createFront(insert, normal, hints, visual, browser) { var _uiUserSettings = []; function applyUserSettings() { for (var cmd of _uiUserSettings) { - frontendCommand(cmd); + self.command(cmd); } } @@ -36,7 +36,7 @@ function createFront(insert, normal, hints, visual, browser) { }); var _callbacks = {}; - function frontendCommand(args, successById) { + self.command = function(args, successById) { args.commandToFrontend = true; args.origin = getDocumentOrigin(); args.id = generateQuickGuid(); @@ -47,7 +47,7 @@ function createFront(insert, normal, hints, visual, browser) { frontendPromise.then(function() { runtime.postTopMessage(args); }); - } + }; document.addEventListener("surfingkeys:setUserSettings", function(evt) { _uiUserSettings.push({ @@ -117,7 +117,7 @@ function createFront(insert, normal, hints, visual, browser) { }; self.executeCommand = function (cmd) { - frontendCommand({ + self.command({ action: 'executeCommand', cmdline: cmd }); @@ -175,14 +175,14 @@ function createFront(insert, normal, hints, visual, browser) { } self.showUsage = function() { - frontendCommand({ + self.command({ action: 'showUsage', metas: getAllAnnotations() }); }; self.getUsage = function(cb) { - frontendCommand({ + self.command({ action: 'getUsage', metas: getAllAnnotations() }, function(response) { @@ -192,14 +192,14 @@ function createFront(insert, normal, hints, visual, browser) { document.addEventListener("surfingkeys:showPopup", function(evt) { const [ content ] = evt.detail; - frontendCommand({ + self.command({ action: 'showPopup', content: content }); }); function hidePopup() { - frontendCommand({ + self.command({ action: 'hidePopup' }); } @@ -279,14 +279,14 @@ function createFront(insert, normal, hints, visual, browser) { if (useNeovim || runtime.conf.useNeovim) { cmd.file_name = `${new URL(window.location.origin).host}/${elementBehindEditor.nodeName.toLowerCase()}`; } - frontendCommand(cmd); + self.command(cmd); }; self.chooseTab = function() { if (normal.repeats !== "") { RUNTIME('focusTabByIndex'); } else { - frontendCommand({ + self.command({ action: 'chooseTab' }); } @@ -319,7 +319,7 @@ function createFront(insert, normal, hints, visual, browser) { */ self.openOmnibar = function(args) { args.action = 'openOmnibar'; - frontendCommand(args); + self.command(args); }; var _inlineQuery; @@ -385,14 +385,14 @@ function createFront(insert, normal, hints, visual, browser) { }; document.addEventListener("surfingkeys:openFinder", function(evt) { - frontendCommand({ + self.command({ action: "openFinder" }); }); document.addEventListener("surfingkeys:showBanner", function(evt) { const [ msg, linger_time ] = evt.detail; - frontendCommand({ + self.command({ action: "showBanner", content: msg, linger_time: linger_time @@ -410,7 +410,7 @@ function createFront(insert, normal, hints, visual, browser) { pos.winX = window.frameElement.offsetLeft; pos.winY = window.frameElement.offsetTop; } - frontendCommand({ + self.command({ action: "showBubble", content: msg, position: pos, @@ -420,7 +420,7 @@ function createFront(insert, normal, hints, visual, browser) { }); document.addEventListener("surfingkeys:hideBubble", function(evt) { - frontendCommand({ + self.command({ action: 'hideBubble' }); }); @@ -434,7 +434,7 @@ function createFront(insert, normal, hints, visual, browser) { document.addEventListener("surfingkeys:hideKeystroke", function(evt) { _keyHints.accumulated = ""; _keyHints.candidates = {}; - frontendCommand({ + self.command({ action: 'hideKeystroke' }); }); @@ -456,7 +456,7 @@ function createFront(insert, normal, hints, visual, browser) { }); } - frontendCommand({ + self.command({ action: 'showKeystroke', keyHints: _keyHints }); @@ -467,7 +467,7 @@ function createFront(insert, normal, hints, visual, browser) { }); self.showStatus = function (pos, msg, duration) { - frontendCommand({ + self.command({ action: "showStatus", content: msg, duration: duration, @@ -475,7 +475,7 @@ function createFront(insert, normal, hints, visual, browser) { }); }; self.toggleStatus = function (visible) { - frontendCommand({ + self.command({ action: "toggleStatus", visible: visible }); @@ -523,7 +523,7 @@ function createFront(insert, normal, hints, visual, browser) { } } - frontendCommand({ + self.command({ action: 'updateOmnibarResult', words: queryResult }); @@ -534,7 +534,7 @@ function createFront(insert, normal, hints, visual, browser) { RUNTIME('executeScript', { code: message.cmdline }, function (response) { - frontendCommand({ + self.command({ action: 'updateOmnibarResult', words: response.response }); @@ -565,7 +565,7 @@ function createFront(insert, normal, hints, visual, browser) { clearPendingQuery(); _pendingQuery = setTimeout(function() { visual.visualUpdate(message.query); - frontendCommand({ + self.command({ action: "visualUpdated" }); }, 500); diff --git a/src/content_scripts/options.js b/src/content_scripts/options.js index f0a2b7f56..cd8674e80 100644 --- a/src/content_scripts/options.js +++ b/src/content_scripts/options.js @@ -77,7 +77,7 @@ function createMappingEditor(elmId) { } if (getBrowserName() === "Firefox") { - document.querySelector("#localPathForSettings").style.display = "none"; + document.querySelector("#localPathForSettings").style.display = ""; document.querySelector("#proxySettings").style.display = "none"; } else if (getBrowserName().startsWith("Safari")) { document.querySelector("#localPathForSettings").style.display = "none"; @@ -221,16 +221,17 @@ function _updateProxy(data) { }); } +var basicSettingsDiv = document.getElementById("basicSettings"); var basicMappingsDiv = document.getElementById("basicMappings"); var advancedSettingDiv = document.getElementById("advancedSetting"); var advancedTogglerDiv = document.getElementById("advancedToggler"); function showAdvanced(flag) { if (flag) { - basicMappingsDiv.hide(); + basicSettingsDiv.hide(); advancedSettingDiv.show(); advancedTogglerDiv.setAttribute('checked', 'checked'); } else { - basicMappingsDiv.show(); + basicSettingsDiv.show(); advancedSettingDiv.hide(); advancedTogglerDiv.removeAttribute('checked'); } @@ -351,6 +352,55 @@ document.addEventListener("surfingkeys:defaultSettingsLoaded", function(evt) { }).filter((m) => m !== null);; }); +function renderSearchAlias(front, disabledSearchAliases) { + const aliasesPromise = new Promise((r, j) => { + const getSearchAliases = () => { + front.command({ + action: 'getSearchAliases' + }, function(response) { + if (Object.keys(response.aliases).length > 0) { + r(response.aliases); + } else { + setTimeout(getSearchAliases, 300); + } + }); + }; + getSearchAliases(); + }); + aliasesPromise.then((aliases) => { + const allAliases = {}; + for (const key in aliases) { + let prompt = aliases[key].prompt; + if (!prompt.startsWith("${prompt}`); + document.querySelector("#searchAliases").appendChild(elm); + + elm.querySelector("input").onchange = () => { + if (disabledSearchAliases.hasOwnProperty(key)) { + delete disabledSearchAliases[key]; + } else { + disabledSearchAliases[key] = prompt; + } + + RUNTIME('updateSettings', { + settings: { + disabledSearchAliases + } + }); + }; + } + }); +} + function renderKeyMappings(rs) { initL10n(function (locale) { var customization = basicMappings.map(function (w, i) { @@ -374,7 +424,9 @@ function renderKeyMappings(rs) { } document.addEventListener("surfingkeys:userSettingsLoaded", function(evt) { - renderKeyMappings(evt.detail.settings); + const { settings, front } = evt.detail; + renderSearchAlias(front, settings.disabledSearchAliases || {}); + renderKeyMappings(settings); }); var KeyPicker = (function() { diff --git a/src/content_scripts/ui/frontend.css b/src/content_scripts/ui/frontend.css index c42ca43ed..96735022b 100644 --- a/src/content_scripts/ui/frontend.css +++ b/src/content_scripts/ui/frontend.css @@ -73,6 +73,7 @@ body { } #sk_omnibarSearchArea>input { display: inline-block; + width: 100%; flex: 1; font-size: 20px; margin-bottom: 0; @@ -83,14 +84,14 @@ body { } #sk_omnibarSearchArea { display: flex; + align-items: center; + border-bottom: 1px solid #999; } .sk_omnibar_middle #sk_omnibarSearchArea { margin: 0.5rem 1rem; - border-bottom: 1px solid #999; } .sk_omnibar_bottom #sk_omnibarSearchArea { margin: 0.2rem 1rem; - border-top: 1px solid #999; } .sk_omnibar_middle #sk_omnibarSearchResult>ul { margin-top: 0; @@ -99,7 +100,7 @@ body { margin-bottom: 0; } #sk_omnibarSearchResult { - max-height: 600px; + max-height: 60vh; overflow: hidden; margin: 0rem 0.6rem; } @@ -392,3 +393,17 @@ div.sk_arrow[dir=up]>div:nth-of-type(2) { z-index: 2147483300 !important; width: 80% !important; } +@media only screen and (max-width: 767px) { + #sk_omnibar { + width: 100%; + left: 0; + } + #sk_omnibarSearchResult { + max-height: 50vh; + overflow: scroll; + } + .sk_omnibar_bottom #sk_omnibarSearchArea { + margin: 0; + padding: 0.2rem; + } +} diff --git a/src/content_scripts/ui/frontend.js b/src/content_scripts/ui/frontend.js index 9d41b125c..43f7070ec 100644 --- a/src/content_scripts/ui/frontend.js +++ b/src/content_scripts/ui/frontend.js @@ -46,8 +46,7 @@ const Front = (function() { const api = createAPI(clipboard, insert, normal, hints, visual, self, {}); - var topOrigin, - _actions = self._actions, + var _actions = self._actions, _callbacks = {}; self.contentCommand = function(args, successById) { args.commandToContent = true; @@ -56,7 +55,11 @@ const Front = (function() { args.ack = true; _callbacks[args.id] = successById; } - top.postMessage({surfingkeys_data: args}, topOrigin); + top.postMessage({surfingkeys_data: args}, self.topOrigin); + }; + + self.postMessage = function(args) { + top.postMessage({surfingkeys_data: args}, self.topOrigin); }; self.addEventListener('keydown', function(event) { @@ -91,7 +94,7 @@ const Front = (function() { action: 'setFrontFrame', pointerEvents: pointerEvents, frameHeight: frameHeight - }}, topOrigin); + }}, self.topOrigin); }; this.nextState = function () { var visibleDivs = Array.from(document.body.querySelectorAll("body>div")).filter(function(n) { @@ -336,7 +339,7 @@ const Front = (function() { data: usage, responseToContent: message.commandToFrontend, id: message.id - }}, topOrigin); + }}, self.topOrigin); }); }; @@ -591,7 +594,7 @@ const Front = (function() { }; _actions['initFrontend'] = function(message) { - topOrigin = message.origin; + self.topOrigin = message.origin; return new Date().getTime(); }; @@ -610,7 +613,7 @@ const Front = (function() { data: ret, action: _message.action + "Ack", responseToContent: _message.commandToFrontend, - }}, topOrigin); + }}, self.topOrigin); } } }, true); diff --git a/src/content_scripts/ui/omnibar.js b/src/content_scripts/ui/omnibar.js index 112982a3b..50f7c5e2b 100644 --- a/src/content_scripts/ui/omnibar.js +++ b/src/content_scripts/ui/omnibar.js @@ -69,6 +69,9 @@ function createOmnibar(front, clipboard) { } event.sk_suppressed = true; }).addEventListener('mousedown', function(event) { + if (!ui.contains(event.target)) { + front.hidePopup(); + } event.sk_suppressed = true; }); @@ -253,11 +256,7 @@ function createOmnibar(front, clipboard) { var lastInput = "", handler, lastHandler = null; var ui = document.getElementById('sk_omnibar'); ui.onclick = function(e) { - if (handler.onClick) { - handler.onClick(e); - } else { - self.input.focus(); - } + self.input.focus(); }; self.triggerInput = function() { @@ -521,6 +520,9 @@ function createOmnibar(front, clipboard) { _savedAargs = args; ui.classList.remove("sk_omnibar_middle"); ui.classList.remove("sk_omnibar_bottom"); + if (getBrowserName() === "Safari-iOS") { + runtime.conf.omnibarPosition = "bottom"; + } ui.classList.add("sk_omnibar_" + runtime.conf.omnibarPosition); if (runtime.conf.omnibarPosition === "bottom") { self.resultsDiv.remove(); @@ -610,6 +612,20 @@ function createOmnibar(front, clipboard) { var li = renderItem(b); if (li) { ul.append(li); + li.onclick = () => { + if (li.url) { + RUNTIME("openLink", { + tab: { + tabbed: true, + active: true, + }, + url: li.url + }); + } else { + self.input.value = li.query; + self.input.focus(); + } + }; } }); self.resultsDiv.append(ul); @@ -1270,10 +1286,31 @@ function SearchEngine(omnibar, front) { url: message.url, suggestionURL: message.suggestionURL }; + const searchEngineIconStorageKey = `surfingkeys.searchEngineIcon.${message.alias}`; + const searchEngineIcon = localStorage.getItem(searchEngineIconStorageKey); + if (searchEngineIcon) { + self.aliases[message.alias].prompt = `${message.prompt}`; + } else if (front.topOrigin.startsWith("http")){ + let origin = new URL(message.url).origin; + origin = origin.replace(/(https?:\/\/)([^.]*)\.([^.]*)\.([^.]*)/, new URL(front.topOrigin).protocol + "//www.$3.$4"); + RUNTIME('requestImage', { + url: `${origin}/favicon.ico` + }, function(response) { + localStorage.setItem(searchEngineIconStorageKey, response.text); + self.aliases[message.alias].prompt = `${message.prompt}`; + }); + } }; front._actions['removeSearchAlias'] = function (message) { delete self.aliases[message.alias]; }; + front._actions['getSearchAliases'] = function (message) { + front.postMessage({ + aliases: self.aliases, + responseToContent: message.commandToFrontend, + id: message.id + }); + }; return self; } diff --git a/src/manifest.json b/src/manifest.json index 9694184a5..a229e62dd 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -36,7 +36,6 @@ "author": "brook hong", "permissions": [ "nativeMessaging", - "proxy", "", "tabs", "history", diff --git a/src/pages/options.css b/src/pages/options.css index aa483e8b6..524bde7fb 100644 --- a/src/pages/options.css +++ b/src/pages/options.css @@ -50,11 +50,22 @@ div.aphost>span.remove { cursor: pointer; } -#basicMappings>div { +#searchAliases>div, #basicMappings>div { display: inline-block; padding: 1px 20px; border-left: 1px solid #ccc; } +#searchAliases div.remove { + cursor: pointer; + display: inline-block; + vertical-align: middle; + height: 32px; + padding-right: 10px; +} +#searchAliases span.prompt { + font-size: 24px; +} + span.annotation { width: 240px; display: inline-block; diff --git a/src/pages/options.html b/src/pages/options.html index 0a171fbf2..e3003c6dd 100644 --- a/src/pages/options.html +++ b/src/pages/options.html @@ -83,10 +83,15 @@

For below hosts, above proxy will be used, click ❌ to remove one.

Pick up a new key, Backsapce to del backward, Enter to confirm, Esc to cancel.

 
-

Key mappings

Advanced mode If you're an advanced user, turn on it here. -
+
+

Search Engine aliases

+
+
+

Key mappings

+
+