diff --git a/README.md b/README.md index 85b0e9136..aa884db33 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,12 @@ Surfingkeys is doing its best to make full use of keyboard for web browsing, but ## Installation + + * [Surfingkeys - Chrome Web Store](https://chrome.google.com/webstore/detail/surfingkeys/gfbliohnnapiefjpjlpjnehglfpaknnc) * [Surfingkeys – Get this Extension for 🦊 Firefox](https://addons.mozilla.org/en-US/firefox/addon/surfingkeys_ff/) * [Surfingkeys - Microsoft Edge Addons](https://microsoftedge.microsoft.com/addons/detail/kgnghhfkloifoabeaobjkgagcecbnppg) +* [Surfingkeys on the Mac App Store](https://apps.apple.com/us/app/surfingkeys/id1599827286) ### TABLE OF CONTENTS diff --git a/config/webpack.config.js b/config/webpack.config.js index 850d69de1..d4b5388a9 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -4,6 +4,7 @@ let buildPath = path.resolve(__dirname, '../dist/'); const CopyWebpackPlugin = require('copy-webpack-plugin'); const FileManagerPlugin = require('filemanager-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin'); function modifyManifest(browser, mode, buffer) { // copy-webpack-plugin passes a buffer @@ -36,21 +37,38 @@ function modifyManifest(browser, mode, buffer) { return manifest_JSON; } -function modifyOptionsHtml(browser, buffer) { - let content = buffer.toString(); - if (browser === "firefox") { - content = content.split("\n").filter((line) => { - return line.indexOf('https://www.google-analytics.com/analytics.js') === -1; - }).join("\n"); - } - return content; -} - module.exports = (env, argv) => { const mode = argv.mode; const browser = env.browser ? env.browser : 'chrome'; buildPath += "/" + browser; - return [{ + const entry = { + background: `./src/background/${browser}.js`, + content: `./src/content_scripts/${browser}.js`, + 'pages/frontend': `./src/content_scripts/ui/frontend.js`, + 'pages/options': './src/content_scripts/options.js', + 'pages/start': './src/content_scripts/start.js', + 'pages/ace': './src/content_scripts/ace.js', + }; + const pagesCopyOptions = { + ignore: [ + '**/neovim.*', + '**/pdf_viewer.html', + ] + }; + if (browser === "chrome") { + pagesCopyOptions.ignore = []; + entry['pages/neovim'] = './src/pages/neovim.js'; + entry['pages/pdf_viewer'] = './src/content_scripts/pdf_viewer.js'; + } + if (browser !== "safari") { + entry['pages/markdown'] = './src/content_scripts/markdown.js'; + } else { + pagesCopyOptions.ignore.push('**/markdown.html'); + pagesCopyOptions.ignore.push('**/donation.png'); + } + console.log(pagesCopyOptions); + + const modules = [{ devtool: false, output: { path: buildPath, @@ -88,21 +106,16 @@ module.exports = (env, argv) => { ], }, target: 'web', - entry: { - 'pages/neovim': './src/pages/neovim.js', - background: `./src/background/${browser}.js`, - content: `./src/content_scripts/${browser}.js`, - 'pages/frontend': `./src/content_scripts/ui/frontend.js`, - 'pages/options': './src/content_scripts/options.js', - 'pages/markdown': './src/content_scripts/markdown.js', - 'pages/pdf_viewer': './src/content_scripts/pdf_viewer.js', - 'pages/start': './src/content_scripts/start.js', - 'pages/ace': './src/content_scripts/ace.js', + entry: entry, + optimization: { + minimizer: [new TerserPlugin({ + extractComments: false, + })], }, plugins: [ new CopyWebpackPlugin({ patterns: [ - { from: 'src/pages', to: 'pages' }, + { from: 'src/pages', to: 'pages', globOptions: pagesCopyOptions }, { from: 'src/content_scripts/ui/frontend.html', to: 'pages' }, { from: 'src/content_scripts/ui/frontend.css', to: 'pages' }, { from: 'node_modules/ace-builds/src-noconflict/worker-javascript.js', to: 'pages' }, @@ -114,16 +127,13 @@ module.exports = (env, argv) => { transform (content, path) { return modifyManifest(browser, mode, content) } - }, - { - from: "src/pages/options.html", - to: "pages", - transform (content, path) { - return modifyOptionsHtml(browser, content) - } } ] - }), + }) + ] + }]; + if (browser !== "safari") { + modules[0].plugins.push( new FileManagerPlugin({ events: { onEnd: { @@ -136,39 +146,48 @@ module.exports = (env, argv) => { }, }, }) - ] - }, { - devtool: false, - output: { - path: buildPath, - filename: '[name].js', - libraryTarget: 'module', - }, - resolve: { - extensions: ['.ts', '.js'], - }, - target: 'web', - entry: { - 'pages/neovim_lib': './src/nvim/renderer.ts', - }, - module: { - rules: [ - { - test: /\.ts$/, - exclude: /node_modules/, - loader: 'ts-loader', - }, - { - test: /\.css$/, - use: [ - { loader: "style-loader", options: { injectType: "linkTag" } }, - { loader: "file-loader" }, - ] - }, - ], - }, - experiments: { - outputModule: true, - } - }] + ); + } + if (browser === "chrome") { + modules.push({ + devtool: false, + output: { + path: buildPath, + filename: '[name].js', + libraryTarget: 'module', + }, + resolve: { + extensions: ['.ts', '.js'], + }, + target: 'web', + entry: { + 'pages/neovim_lib': './src/nvim/renderer.ts', + }, + module: { + rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + loader: 'ts-loader', + }, + { + test: /\.css$/, + use: [ + { loader: "style-loader", options: { injectType: "linkTag" } }, + { loader: "file-loader" }, + ] + }, + ], + }, + optimization: { + minimizer: [new TerserPlugin({ + extractComments: false, + })], + }, + experiments: { + outputModule: true, + } + }); + } + return modules; }; diff --git a/package.json b/package.json index d18b6b7f7..dd21d4f49 100644 --- a/package.json +++ b/package.json @@ -11,11 +11,13 @@ "clean": "rm -rf dist/*", "build:doc": "documentation build src/content_scripts/common/api.js src/content_scripts/common/clipboard.js src/content_scripts/common/hints.js src/content_scripts/common/visual.js src/content_scripts/front.js -f md -o docs/api.md", "build:dev-firefox": "webpack --env=browser=firefox --mode=development --config ./config/webpack.config.js", + "build:dev-safari": "webpack --env=browser=safari --mode=development --config ./config/webpack.config.js", "build:dev": "webpack --mode=development --config ./config/webpack.config.js", "build:prod-firefox": "webpack --env=browser=firefox --mode=production --config ./config/webpack.config.js", + "build:prod-safari": "webpack --env=browser=safari --mode=production --config ./config/webpack.config.js", "build:prod": "webpack --mode=production --config ./config/webpack.config.js", "build:testdata": "webpack --mode=production --config ./config/webpack.test.config.js", - "build": "npm-run-all clean test build:doc build:prod build:prod-firefox", + "build": "npm-run-all clean test build:doc build:prod build:prod-firefox build:prod-safari", "test": "jest" }, "repository": { @@ -43,6 +45,7 @@ "documentation": "^13.2.5", "file-loader": "^6.2.0", "filemanager-webpack-plugin": "^6.1.7", + "jest": "^27.3.1", "jest-image-snapshot": "^4.5.1", "npm-run-all": "^4.1.5", "puppeteer": "^10.2.0", diff --git a/sk.svg b/sk.svg new file mode 100644 index 000000000..d369fbe0d --- /dev/null +++ b/sk.svg @@ -0,0 +1,3 @@ + + + diff --git a/sk.xcf b/sk.xcf index 39ca36280..284b93a27 100644 Binary files a/sk.xcf and b/sk.xcf differ diff --git a/src/background/safari.js b/src/background/safari.js new file mode 100644 index 000000000..a7e6702fa --- /dev/null +++ b/src/background/safari.js @@ -0,0 +1,55 @@ +import { + _save, + dictFromArray, + extendObject, + getSubSettings, + start +} from './start.js'; + +function loadRawSettings(keys, cb, defaultSet) { + var rawSet = defaultSet || {}; + chrome.storage.local.get(null, function(localSet) { + var localSavedAt = localSet.savedAt || 0; + chrome.storage.sync.get(null, function(syncSet) { + var syncSavedAt = syncSet.savedAt || 0; + if (localSavedAt > syncSavedAt) { + extendObject(rawSet, localSet); + _save(chrome.storage.sync, localSet, function() { + var subset = getSubSettings(rawSet, keys); + if (chrome.runtime.lastError) { + subset.error = "Settings sync may not work thoroughly because of: " + chrome.runtime.lastError.message; + } + cb(subset); + }); + } else if (localSavedAt < syncSavedAt) { + extendObject(rawSet, syncSet); + cb(getSubSettings(rawSet, keys)); + _save(chrome.storage.local, syncSet); + } else { + extendObject(rawSet, localSet); + cb(getSubSettings(rawSet, keys)); + } + }); + }); +} + +function _applyProxySettings(proxyConf) { +} + +function _setNewTabUrl(){ + return "favorites://"; +} + +function _getContainerName(self, _response){ +} + +function getLatestHistoryItem(text, maxResults, cb) { +} + +start({ + getLatestHistoryItem, + loadRawSettings, + _applyProxySettings, + _setNewTabUrl, + _getContainerName +}); diff --git a/src/background/start.js b/src/background/start.js index 81cf1c9bf..5c83f9f73 100644 --- a/src/background/start.js +++ b/src/background/start.js @@ -524,7 +524,7 @@ function start(browser) { loadSettings('blocklist', function(data) { var origin = ".*"; var senderOrigin = sender.origin || new URL(getSenderUrl(sender)).origin; - if (chrome.extension.getURL("").indexOf(senderOrigin) !== 0) { + if (chrome.extension.getURL("/").indexOf(senderOrigin) !== 0 && senderOrigin !== "null") { origin = senderOrigin; } if (data.blocklist.hasOwnProperty(origin)) { @@ -543,7 +543,7 @@ function start(browser) { }; self.toggleMouseQuery = function(message, sender, sendResponse) { loadSettings('mouseSelectToQuery', function(data) { - if (sender.tab && sender.tab.url.indexOf(chrome.extension.getURL("")) !== 0) { + if (sender.tab && sender.tab.url.indexOf(chrome.extension.getURL("/")) !== 0) { var mouseSelectToQuery = data.mouseSelectToQuery || []; var idx = mouseSelectToQuery.indexOf(message.origin); if (idx === -1) { @@ -659,12 +659,18 @@ function start(browser) { }); }; self.getTopSites = function(message, sender, sendResponse) { - chrome.topSites.get(function(urls) { - urls = _filterByTitleOrUrl(urls, message.query); + if (chrome.topSites) { + chrome.topSites.get(function(urls) { + urls = _filterByTitleOrUrl(urls, message.query); + _response(message, sendResponse, { + urls: urls + }); + }); + } else { _response(message, sendResponse, { - urls: urls + urls: [] }); - }); + } }; @@ -1607,6 +1613,9 @@ function start(browser) { } chrome.tabs.reload(sender.tab.id); }; + self.writeClipboard = function (message, sender, sendResponse) { + navigator.clipboard.writeText(message.text) + }; self.getContainerName = browser._getContainerName(self, _response); chrome.runtime.setUninstallURL("http://brookhong.github.io/2018/01/30/why-did-you-uninstall-surfingkeys.html"); diff --git a/src/content_scripts/common/api.js b/src/content_scripts/common/api.js index a352ee7ef..7fb7d5f18 100644 --- a/src/content_scripts/common/api.js +++ b/src/content_scripts/common/api.js @@ -4,6 +4,7 @@ import KeyboardUtils from './keyboardUtils'; import { actionWithSelectionPreserved, constructSearchURL, + getBrowserName, getClickableElements, getRealEdit, getTextNodePos, @@ -11,6 +12,7 @@ import { htmlEncode, mapInMode, parseAnnotation, + setSanitizedContent, showBanner, showPopup, tabOpenLink, @@ -433,23 +435,39 @@ function createAPI(clipboard, insert, normal, hints, visual, front, browser) { imapkey('', '#15Open vim editor for current input', function() { openVim(false); }); - imapkey('', '#15Open neovim for current input', function() { - openVim(true); - }); - - function toggleProxySite(host) { - RUNTIME('updateProxy', { - host: host, - operation: "toggle" + const browserName = getBrowserName(); + if (browserName === "Chrome") { + imapkey('', '#15Open neovim for current input', function() { + openVim(true); + }); + mapkey(';s', 'Toggle PDF viewer from SurfingKeys', function() { + var pdfUrl = window.location.href; + if (pdfUrl.indexOf(chrome.extension.getURL("/pages/pdf_viewer.html")) === 0) { + pdfUrl = window.location.search.substr(3); + chrome.storage.local.set({"noPdfViewer": 1}, function() { + window.location.replace(pdfUrl); + }); + } else { + if (document.querySelector("EMBED") && document.querySelector("EMBED").getAttribute("type") === "application/pdf") { + chrome.storage.local.remove("noPdfViewer", function() { + window.location.replace(pdfUrl); + }); + } else { + chrome.storage.local.get("noPdfViewer", function(resp) { + if(!resp.noPdfViewer) { + chrome.storage.local.set({"noPdfViewer": 1}, function() { + showBanner("PDF viewer disabled."); + }); + } else { + chrome.storage.local.remove("noPdfViewer", function() { + showBanner("PDF viewer enabled."); + }); + } + }); + } + } }); - return true; } - mapkey('cp', '#13Toggle proxy for current site', function() { - var host = window.location.host.replace(/:\d+/,''); - if (host && host.length) { - toggleProxySite(host); - } - }); mapkey(";ql", '#0Show last action', function() { showPopup(htmlEncode(runtime.conf.lastKeys.map(function(k) { @@ -461,34 +479,6 @@ function createAPI(clipboard, insert, normal, hints, visual, front, browser) { hints.createInputLayer(); }); - mapkey(';s', 'Toggle PDF viewer from SurfingKeys', function() { - var pdfUrl = window.location.href; - if (pdfUrl.indexOf(chrome.extension.getURL("/pages/pdf_viewer.html")) === 0) { - pdfUrl = window.location.search.substr(3); - chrome.storage.local.set({"noPdfViewer": 1}, function() { - window.location.replace(pdfUrl); - }); - } else { - if (document.querySelector("EMBED") && document.querySelector("EMBED").getAttribute("type") === "application/pdf") { - chrome.storage.local.remove("noPdfViewer", function() { - window.location.replace(pdfUrl); - }); - } else { - chrome.storage.local.get("noPdfViewer", function(resp) { - if(!resp.noPdfViewer) { - chrome.storage.local.set({"noPdfViewer": 1}, function() { - showBanner("PDF viewer disabled."); - }); - } else { - chrome.storage.local.remove("noPdfViewer", function() { - showBanner("PDF viewer enabled."); - }); - } - }); - } - } - }); - mapkey('zv', '#9Enter visual mode, and select whole element', function() { visual.toggle("z"); }); @@ -690,6 +680,7 @@ function createAPI(clipboard, insert, normal, hints, visual, front, browser) { cmap, imap, imapkey, + getBrowserName, getClickableElements, getFormData, map, diff --git a/src/content_scripts/common/clipboard.js b/src/content_scripts/common/clipboard.js index 9799ca6af..622efecc8 100644 --- a/src/content_scripts/common/clipboard.js +++ b/src/content_scripts/common/clipboard.js @@ -1,6 +1,8 @@ +import { RUNTIME } from './runtime.js'; import { actionWithSelectionPreserved, insertJS, + getBrowserName, setSanitizedContent, showBanner, } from './utils.js'; @@ -36,7 +38,7 @@ function createClipboard() { * }); */ self.read = function(onReady) { - if (window.navigator.userAgent.indexOf("Firefox") !== -1 && + if (getBrowserName() === "Firefox" && typeof navigator.clipboard === 'object' && typeof navigator.clipboard.readText === 'function') { navigator.clipboard.readText().then((data) => { // call back onReady in a different thread to avoid breaking UI operations @@ -73,27 +75,28 @@ function createClipboard() { const cb = () => { showBanner("Copied: " + text); }; - if (window.navigator.userAgent.indexOf("Firefox") !== -1 && - typeof navigator.clipboard === 'object' && typeof navigator.clipboard.writeText === 'function') { - navigator.clipboard.writeText(text).then(cb); - return; - } - insertJS(function() { - window.oncopy = document.oncopy; - document.oncopy = null; - }, function() { - clipboardActionWithSelectionPreserved(function() { - holder.value = text; - holder.select(); - document.execCommand('copy'); - holder.value = ''; - }); + if (getBrowserName() === "Chrome") { insertJS(function() { - document.oncopy = window.oncopy; - delete window.oncopy; + window.oncopy = document.oncopy; + document.oncopy = null; + }, function() { + clipboardActionWithSelectionPreserved(function() { + holder.value = text; + holder.select(); + document.execCommand('copy'); + holder.value = ''; + }); + insertJS(function() { + document.oncopy = window.oncopy; + delete window.oncopy; + }); + cb(); }); + } else { + // works for Firefox and Safari now. + RUNTIME("writeClipboard", { text }); cb(); - }); + } }; return self; diff --git a/src/content_scripts/common/default.js b/src/content_scripts/common/default.js index 4df3bec0b..5ca68f9e0 100644 --- a/src/content_scripts/common/default.js +++ b/src/content_scripts/common/default.js @@ -2,6 +2,7 @@ module.exports = function(api) { const { addSearchAlias, cmap, + getBrowserName, getFormData, map, mapkey, @@ -14,38 +15,6 @@ module.exports = function(api) { RUNTIME } = api; - mapkey(';cp', '#13Copy proxy info', function() { - RUNTIME('getSettings', { - key: ['proxyMode', 'proxy', 'autoproxy_hosts'] - }, function(response) { - Clipboard.write(JSON.stringify(response.settings, null, 4)); - }); - }); - mapkey(';ap', '#13Apply proxy info from clipboard', function() { - Clipboard.read(function(response) { - var proxyConf = JSON.parse(response.data); - RUNTIME('updateProxy', { - operation: 'set', - host: proxyConf.autoproxy_hosts, - proxy: proxyConf.proxy, - mode: proxyConf.proxyMode - }); - }); - }); - // create shortcuts for the command with different parameters - map(';pa', ':setProxyMode always', 0, '#13set proxy mode `always`'); - map(';pb', ':setProxyMode byhost', 0, '#13set proxy mode `byhost`'); - map(';pd', ':setProxyMode direct', 0, '#13set proxy mode `direct`'); - map(';ps', ':setProxyMode system', 0, '#13set proxy mode `system`'); - map(';pc', ':setProxyMode clear', 0, '#13set proxy mode `clear`'); - mapkey('gr', '#14Read selected text or text from clipboard', function() { - Clipboard.read(function(response) { - readText(window.getSelection().toString() || response.data, {verbose: true}); - }); - }); - vmapkey('gr', '#9Read selected text', function() { - readText(window.getSelection().toString(), {verbose: true}); - }); map('g0', ':feedkeys 99E', 0, "#3Go to the first tab"); map('g$', ':feedkeys 99R', 0, "#3Go to the last tab"); mapkey('zr', '#3zoom reset', function() { @@ -152,11 +121,6 @@ module.exports = function(api) { Front.showEditor(element); }); }); - mapkey('', '#1Go to edit box with neo vim editor', function() { - Hints.create("input, textarea, *[contenteditable=true], select", function(element) { - Front.showEditor(element, null, null, true); - }); - }); map('', 'I'); cmap('', ''); @@ -196,36 +160,15 @@ module.exports = function(api) { mapkey('r', '#4Reload the page', function() { RUNTIME("reloadTab", { nocache: false }); }); - mapkey('t', '#8Open a URL', function() { - Front.openOmnibar({type: "URLs"}); - }); - mapkey('go', '#8Open a URL in current tab', function() { - Front.openOmnibar({type: "URLs", tabbed: false}); - }); mapkey('oi', '#8Open incognito window', function() { RUNTIME('openIncognito', { url: window.location.href }); }); - mapkey('ox', '#8Open recently closed URL', function() { - Front.openOmnibar({type: "RecentlyClosed"}); - }); + mapkey('H', '#8Open opened URL in current tab', function() { Front.openOmnibar({type: "TabURLs"}); }); - mapkey('b', '#8Open a bookmark', function() { - Front.openOmnibar(({type: "Bookmarks"})); - }); - mapkey('ab', '#8Bookmark current page to selected folder', function() { - var page = { - url: window.location.href, - title: document.title - }; - Front.openOmnibar(({type: "AddBookmark", extra: page})); - }); - mapkey('oh', '#8Open URL from history', function() { - Front.openOmnibar({type: "History"}); - }); mapkey('om', '#8Open URL from vim-like marks', function() { Front.openOmnibar({type: "VIMarks"}); }); @@ -240,30 +183,6 @@ module.exports = function(api) { mapkey('x', '#3Close current tab', function() { RUNTIME("closeTab"); }); - mapkey('X', '#3Restore closed tab', function() { - RUNTIME("openLast"); - }); - mapkey('W', '#3Move current tab to another window', function() { - Front.openOmnibar(({type: "Windows"})); - }); - mapkey(';gt', '#3Gather filtered tabs into current window', function() { - Front.openOmnibar({type: "Tabs", extra: { - action: "gather" - }}); - }); - mapkey(';gw', '#3Gather all tabs into current window', function() { - RUNTIME("gatherWindows"); - }); - mapkey('<<', '#3Move current tab to left', function() { - RUNTIME('moveTab', { - step: -1 - }); - }); - mapkey('>>', '#3Move current tab to right', function() { - RUNTIME('moveTab', { - step: 1 - }); - }); mapkey(';w', '#2Focus top window', function() { top.focus(); }); @@ -294,16 +213,6 @@ module.exports = function(api) { }); }); }); - mapkey('yd', "#7Copy current downloading URL", function() { - RUNTIME('getDownloads', { - query: {state: "in_progress"} - }, function(response) { - var items = response.downloads.map(function(o) { - return o.url; - }); - Clipboard.write(items.join(',')); - }); - }); mapkey('yt', '#3Duplicate current tab', function() { RUNTIME("duplicateTab"); }); @@ -406,45 +315,7 @@ module.exports = function(api) { mapkey('oy', '#8Open Search with alias y', function() { Front.openOmnibar({type: "SearchEngine", extra: "y"}); }); - if (window.navigator.userAgent.indexOf("Firefox") > 0) { - mapkey('on', '#3Open newtab', function() { - tabOpenLink("about:blank"); - }); - } else { - mapkey('on', '#3Open newtab', function() { - tabOpenLink("chrome://newtab/"); - }); - mapkey('ga', '#12Open Chrome About', function() { - tabOpenLink("chrome://help/"); - }); - mapkey('gb', '#12Open Chrome Bookmarks', function() { - tabOpenLink("chrome://bookmarks/"); - }); - mapkey('gc', '#12Open Chrome Cache', function() { - tabOpenLink("chrome://cache/"); - }); - mapkey('gd', '#12Open Chrome Downloads', function() { - tabOpenLink("chrome://downloads/"); - }); - mapkey('gh', '#12Open Chrome History', function() { - tabOpenLink("chrome://history/"); - }); - mapkey('gk', '#12Open Chrome Cookies', function() { - tabOpenLink("chrome://settings/content/cookies"); - }); - mapkey('ge', '#12Open Chrome Extensions', function() { - tabOpenLink("chrome://extensions/"); - }); - mapkey('gn', '#12Open Chrome net-internals', function() { - tabOpenLink("chrome://net-internals/#proxy"); - }); - mapkey(';i', '#12Open Chrome Inspect', function() { - tabOpenLink("chrome://inspect/#devices"); - }); - } - mapkey('gs', '#12View page source', function() { - RUNTIME("viewSource", { tab: { tabbed: true }}); - }); + mapkey('g?', '#4Reload current page without query string(all parts after question mark)', function() { window.location.href = window.location.href.replace(/\?[^\?]*$/, ''); }); @@ -472,12 +343,6 @@ module.exports = function(api) { mapkey(';e', '#11Edit Settings', function() { tabOpenLink("/pages/options.html"); }); - mapkey(';v', '#11Open neovim', function() { - tabOpenLink("/pages/neovim.html"); - }); - mapkey(';pm', '#11Preview markdown', function() { - tabOpenLink("/pages/markdown.html"); - }); mapkey(';u', '#4Edit current URL with vim editor, and open in new tab', function() { Front.showEditor(window.location.href, function(data) { tabOpenLink(data); @@ -488,35 +353,6 @@ module.exports = function(api) { window.location.href = data; }, 'url'); }); - mapkey(';di', '#1Download image', function() { - Hints.create('img', function(element) { - RUNTIME('download', { - url: element.src - }); - }); - }); - mapkey(';j', '#12Close Downloads Shelf', function() { - RUNTIME("closeDownloadsShelf", {clearHistory: true}); - }); - - mapkey(';dh', '#14Delete history older than 30 days', function() { - RUNTIME('deleteHistoryOlderThan', { - days: 30 - }); - }); - mapkey(';yh', '#14Yank histories', function() { - RUNTIME('getHistory', {}, function(response) { - Clipboard.write(response.history.map(h => h.url).join("\n")); - }); - }); - mapkey(';ph', '#14Put histories from clipboard', function() { - Clipboard.read(function(response) { - RUNTIME('addHistories', {history: response.data.split("\n")}); - }); - }); - mapkey(';db', '#14Remove bookmark for current page', function() { - RUNTIME('removeBookmark'); - }); addSearchAlias('g', 'google', 'https://www.google.com/search?q=', 's', 'https://www.google.com/complete/search?client=chrome-omni&gs_ri=chrome-ext&oit=1&cp=1&pgcl=7&q=', function(response) { var res = JSON.parse(response.text); @@ -557,4 +393,184 @@ module.exports = function(api) { }); }); + const browser = getBrowserName(); + if (browser === "Firefox") { + mapkey('on', '#3Open newtab', function() { + tabOpenLink("about:blank"); + }); + } else if (browser === "Chrome") { + mapkey('cp', '#13Toggle proxy for current site', function() { + var host = window.location.host.replace(/:\d+/,''); + if (host && host.length) { + RUNTIME('updateProxy', { + host: host, + operation: "toggle" + }); + } + }); + mapkey(';cp', '#13Copy proxy info', function() { + RUNTIME('getSettings', { + key: ['proxyMode', 'proxy', 'autoproxy_hosts'] + }, function(response) { + Clipboard.write(JSON.stringify(response.settings, null, 4)); + }); + }); + mapkey(';ap', '#13Apply proxy info from clipboard', function() { + Clipboard.read(function(response) { + var proxyConf = JSON.parse(response.data); + RUNTIME('updateProxy', { + operation: 'set', + host: proxyConf.autoproxy_hosts, + proxy: proxyConf.proxy, + mode: proxyConf.proxyMode + }); + }); + }); + // create shortcuts for the command with different parameters + map(';pa', ':setProxyMode always', 0, '#13set proxy mode `always`'); + map(';pb', ':setProxyMode byhost', 0, '#13set proxy mode `byhost`'); + map(';pd', ':setProxyMode direct', 0, '#13set proxy mode `direct`'); + map(';ps', ':setProxyMode system', 0, '#13set proxy mode `system`'); + map(';pc', ':setProxyMode clear', 0, '#13set proxy mode `clear`'); + mapkey('gr', '#14Read selected text or text from clipboard', function() { + Clipboard.read(function(response) { + readText(window.getSelection().toString() || response.data, {verbose: true}); + }); + }); + vmapkey('gr', '#9Read selected text', function() { + readText(window.getSelection().toString(), {verbose: true}); + }); + + mapkey('on', '#3Open newtab', function() { + tabOpenLink("chrome://newtab/"); + }); + mapkey('ga', '#12Open Chrome About', function() { + tabOpenLink("chrome://help/"); + }); + mapkey('gb', '#12Open Chrome Bookmarks', function() { + tabOpenLink("chrome://bookmarks/"); + }); + mapkey('gc', '#12Open Chrome Cache', function() { + tabOpenLink("chrome://cache/"); + }); + mapkey('gd', '#12Open Chrome Downloads', function() { + tabOpenLink("chrome://downloads/"); + }); + mapkey('gh', '#12Open Chrome History', function() { + tabOpenLink("chrome://history/"); + }); + mapkey('gk', '#12Open Chrome Cookies', function() { + tabOpenLink("chrome://settings/content/cookies"); + }); + mapkey('ge', '#12Open Chrome Extensions', function() { + tabOpenLink("chrome://extensions/"); + }); + mapkey('gn', '#12Open Chrome net-internals', function() { + tabOpenLink("chrome://net-internals/#proxy"); + }); + mapkey(';i', '#12Open Chrome Inspect', function() { + tabOpenLink("chrome://inspect/#devices"); + }); + mapkey(';v', '#11Open neovim', function() { + tabOpenLink("/pages/neovim.html"); + }); + mapkey('', '#1Go to edit box with neo vim editor', function() { + Hints.create("input, textarea, *[contenteditable=true], select", function(element) { + Front.showEditor(element, null, null, true); + }); + }); + } + + if (browser !== "Safari") { + mapkey('t', '#8Open a URL', function() { + Front.openOmnibar({type: "URLs"}); + }); + mapkey('go', '#8Open a URL in current tab', function() { + Front.openOmnibar({type: "URLs", tabbed: false}); + }); + mapkey('ox', '#8Open recently closed URL', function() { + Front.openOmnibar({type: "RecentlyClosed"}); + }); + mapkey('X', '#3Restore closed tab', function() { + RUNTIME("openLast"); + }); + mapkey('b', '#8Open a bookmark', function() { + Front.openOmnibar(({type: "Bookmarks"})); + }); + mapkey('ab', '#8Bookmark current page to selected folder', function() { + var page = { + url: window.location.href, + title: document.title + }; + Front.openOmnibar(({type: "AddBookmark", extra: page})); + }); + mapkey('oh', '#8Open URL from history', function() { + Front.openOmnibar({type: "History"}); + }); + mapkey('W', '#3Move current tab to another window', function() { + Front.openOmnibar(({type: "Windows"})); + }); + mapkey(';gt', '#3Gather filtered tabs into current window', function() { + Front.openOmnibar({type: "Tabs", extra: { + action: "gather" + }}); + }); + mapkey(';gw', '#3Gather all tabs into current window', function() { + RUNTIME("gatherWindows"); + }); + mapkey('<<', '#3Move current tab to left', function() { + RUNTIME('moveTab', { + step: -1 + }); + }); + mapkey('>>', '#3Move current tab to right', function() { + RUNTIME('moveTab', { + step: 1 + }); + }); + mapkey('yd', "#7Copy current downloading URL", function() { + RUNTIME('getDownloads', { + query: {state: "in_progress"} + }, function(response) { + var items = response.downloads.map(function(o) { + return o.url; + }); + Clipboard.write(items.join(',')); + }); + }); + mapkey('gs', '#12View page source', function() { + RUNTIME("viewSource", { tab: { tabbed: true }}); + }); + mapkey(';pm', '#11Preview markdown', function() { + tabOpenLink("/pages/markdown.html"); + }); + mapkey(';di', '#1Download image', function() { + Hints.create('img', function(element) { + RUNTIME('download', { + url: element.src + }); + }); + }); + mapkey(';j', '#12Close Downloads Shelf', function() { + RUNTIME("closeDownloadsShelf", {clearHistory: true}); + }); + mapkey(';dh', '#14Delete history older than 30 days', function() { + RUNTIME('deleteHistoryOlderThan', { + days: 30 + }); + }); + mapkey(';yh', '#14Yank histories', function() { + RUNTIME('getHistory', {}, function(response) { + Clipboard.write(response.history.map(h => h.url).join("\n")); + }); + }); + mapkey(';ph', '#14Put histories from clipboard', function() { + Clipboard.read(function(response) { + RUNTIME('addHistories', {history: response.data.split("\n")}); + }); + }); + mapkey(';db', '#14Remove bookmark for current page', function() { + RUNTIME('removeBookmark'); + }); + } } diff --git a/src/content_scripts/common/hints.js b/src/content_scripts/common/hints.js index 6bb479750..74222e267 100644 --- a/src/content_scripts/common/hints.js +++ b/src/content_scripts/common/hints.js @@ -7,6 +7,7 @@ import { flashPressedLink, getElements, listElements, + getBrowserName, getClickableElements, getRealRect, getTextNodePos, @@ -750,7 +751,7 @@ div.hint-scrollable { if (shiftKey && runtime.conf.hintShiftNonActive) { tabbed = true; active = false; - } else if (shiftKey && window.navigator.userAgent.indexOf("Firefox") !== -1) { + } else if (shiftKey && getBrowserName() === "Firefox") { // mouseButton does not work for firefox in mouse event. tabbed = true; active = true; diff --git a/src/content_scripts/common/normal.js b/src/content_scripts/common/normal.js index 1a57fba1c..9fbd0d2c5 100644 --- a/src/content_scripts/common/normal.js +++ b/src/content_scripts/common/normal.js @@ -194,7 +194,7 @@ function createNormal(insert) { }); self.toggleBlocklist = function() { - if (document.location.href.indexOf(chrome.extension.getURL("")) !== 0) { + if (document.location.href.indexOf(chrome.extension.getURL("/")) !== 0) { RUNTIME('toggleBlocklist', { blocklistPattern: (runtime.conf.blocklistPattern ? runtime.conf.blocklistPattern.toJSON() : "") }, function(resp) { diff --git a/src/content_scripts/common/utils.js b/src/content_scripts/common/utils.js index 688724915..e59185431 100644 --- a/src/content_scripts/common/utils.js +++ b/src/content_scripts/common/utils.js @@ -2,8 +2,24 @@ import * as DOMPurify from 'dompurify'; import KeyboardUtils from './keyboardUtils'; import { RUNTIME, dispatchSKEvent, runtime } from './runtime.js'; +/** + * Get current browser name + * @returns {string} "Chrome" | "Firefox" | "Safari" + * + */ +function getBrowserName() { + if (window.navigator.userAgent.indexOf("Chrome") !== -1) { + return "Chrome"; + } else if (window.navigator.vendor.indexOf("Apple Computer, Inc.") === 0) { + return "Safari"; + } else if (window.navigator.userAgent.indexOf("Firefox") !== -1) { + return "Firefox"; + } + return "Chrome"; +} + function isInUIFrame() { - return document.location.href.indexOf(chrome.extension.getURL("")) === 0; + return document.location.href.indexOf(chrome.extension.getURL("/")) === 0; } function timeStampString(t) { @@ -720,6 +736,7 @@ export { flashPressedLink, generateQuickGuid, getAnnotations, + getBrowserName, getClickableElements, getDocumentOrigin, getElements, diff --git a/src/content_scripts/common/visual.js b/src/content_scripts/common/visual.js index 437deb9c3..64e291933 100644 --- a/src/content_scripts/common/visual.js +++ b/src/content_scripts/common/visual.js @@ -6,6 +6,7 @@ import { actionWithSelectionPreserved, dispatchMouseEvent, flashPressedLink, + getBrowserName, getTextNodes, getTextRect, getVisibleElements, @@ -157,7 +158,7 @@ function createVisual(clipboard, hints) { feature_group: 9, code: function() { document.scrollingElement.scrollTop = document.scrollingElement.scrollHeight; - if (window.navigator.userAgent.indexOf("Firefox") === -1) { + if (getBrowserName() !== "Firefox") { modifySelection(); } else { self.hideCursor(); @@ -183,7 +184,7 @@ function createVisual(clipboard, hints) { dispatchSKEvent('showStatus', [2, currentOccurrence + 1 + ' / ' + matches.length]); } - if (window.navigator.userAgent.indexOf("Firefox") === -1) { + if (getBrowserName() !== "Firefox") { modifySelection(); } else { self.hideCursor(); @@ -211,7 +212,7 @@ function createVisual(clipboard, hints) { p: "paragraphboundary" }; function _selectUnit(w) { - if (window.navigator.userAgent.indexOf("Firefox") === -1 || (w !== "p" && w !== "s")) { + if (getBrowserName() !== "Firefox" || (w !== "p" && w !== "s")) { var unit = _units[w]; // sentence and paragraphboundary not support in firefox // document.getSelection().modify("move", "backward", "paragraphboundary") diff --git a/src/content_scripts/content.js b/src/content_scripts/content.js index efc2dca28..f65871367 100644 --- a/src/content_scripts/content.js +++ b/src/content_scripts/content.js @@ -250,7 +250,7 @@ function start(browser) { } else { // activate Modes in the frames on extension pages runtime.getTopURL(function(u) { - if (u.indexOf(chrome.extension.getURL("")) === 0) { + if (u.indexOf(chrome.extension.getURL("/")) === 0) { _initContent(_initModules()); } }); diff --git a/src/content_scripts/front.js b/src/content_scripts/front.js index 89dc5d737..a712250b7 100644 --- a/src/content_scripts/front.js +++ b/src/content_scripts/front.js @@ -3,6 +3,7 @@ import { flashPressedLink, generateQuickGuid, getAnnotations, + getBrowserName, getDocumentOrigin, httpRequest, isEditable, @@ -507,7 +508,7 @@ function createFront(insert, normal, hints, visual) { if (queryResult.constructor.name !== "Array") { queryResult = [queryResult]; } - if (window.navigator.userAgent.indexOf("Firefox") === -1) { + if (getBrowserName() === "Chrome") { var sentence = visual.findSentenceOf(response.query); if (sentence.length > 0) { queryResult.push(sentence); @@ -556,7 +557,7 @@ function createFront(insert, normal, hints, visual) { clearPendingQuery(); _pendingQuery = setTimeout(function() { visual.visualUpdateForContentWindow(message.query); - if (window.navigator.userAgent.indexOf("Firefox") !== -1) { + if (getBrowserName() === "Firefox") { frontendCommand({ action: "visualUpdatedForFirefox" }); diff --git a/src/content_scripts/options.js b/src/content_scripts/options.js index 29367fc2d..bbb90be46 100644 --- a/src/content_scripts/options.js +++ b/src/content_scripts/options.js @@ -4,6 +4,7 @@ import Mode from './common/mode'; import { createElementWithContent, generateQuickGuid, + getBrowserName, htmlEncode, httpRequest, initL10n, @@ -12,15 +13,6 @@ import { showBanner, } from './common/utils.js'; -var defaultMappingsEditor = ace.edit("defaultMappings"); -defaultMappingsEditor.setTheme("ace/theme/chrome"); -defaultMappingsEditor.setKeyboardHandler('ace/keyboard/vim'); -defaultMappingsEditor.getSession().setMode("ace/mode/javascript"); -defaultMappingsEditor.container.hide(); -defaultMappingsEditor.setReadOnly(true); -defaultMappingsEditor.container.style.background="#f1f1f1"; -defaultMappingsEditor.$blockScrolling = Infinity; - var mappingsEditor = null; function createMappingEditor(elmId) { var _ace = ace.edit(elmId); @@ -84,8 +76,13 @@ function createMappingEditor(elmId) { return self; } -if (window.navigator.userAgent.indexOf("Firefox") !== -1) { +if (getBrowserName() === "Firefox") { + document.querySelector("#localPathForSettings").style.display = "none"; + document.querySelector("#proxySettings").style.display = "none"; +} else if (getBrowserName() === "Safari") { + document.querySelector("#localPathForSettings").style.display = "none"; document.querySelector("#proxySettings").style.display = "none"; + document.querySelector("#donationDiv").style.display = "none"; } var proxyModeSelect = document.querySelector("#proxyMode>select"); var proxyGroup = document.getElementById("proxyMode").parentElement; @@ -250,7 +247,6 @@ function renderSettings(rs) { } var h = window.innerHeight / 2; mappingsEditor.container.style.height = h + "px"; - defaultMappingsEditor.container.style.height = h + "px"; if (rs.snippets && rs.snippets.length) { mappingsEditor.setValue(rs.snippets, -1); } else { @@ -298,22 +294,6 @@ document.querySelector('.infoPointer').onclick = function() { } }; -document.getElementById('showDefaultSettings').onclick = function() { - if (defaultMappingsEditor.container.style.display !== "none") { - defaultMappingsEditor.container.hide(); - mappingsEditor.container.style.width = "100%"; - } else { - httpRequest({ - url: chrome.extension.getURL('/pages/default.js'), - }, function(res) { - defaultMappingsEditor.container.style.display = "inline-block"; - defaultMappingsEditor.setValue(res.text, -1); - defaultMappingsEditor.container.style.width = "50%"; - }); - mappingsEditor.container.style.width = "50%"; - } -}; - function getURIPath(fn) { if (fn.length && !/^\w+:\/\/\w+/i.test(fn) && fn.indexOf('file:///') === -1) { fn = fn.replace(/\\/g, '/'); @@ -359,11 +339,16 @@ var basicMappings = ['d', 'R', 'f', 'E', 'e', 'x', 'gg', 'j', '/', 'n', 'r', 'k' document.addEventListener("surfingkeys:defaultSettingsLoaded", function(evt) { const { normal } = evt.detail; basicMappings = basicMappings.map(function(w, i) { - return { - origin: w, - annotation: normal.mappings.find(KeyboardUtils.encodeKeystroke(w)).meta.annotation - }; - }); + const binding = normal.mappings.find(KeyboardUtils.encodeKeystroke(w)); + if (binding) { + return { + origin: w, + annotation: binding.meta.annotation + }; + } else { + return null; + } + }).filter((m) => m !== null);; }); function renderKeyMappings(rs) { diff --git a/src/content_scripts/safari.js b/src/content_scripts/safari.js new file mode 100644 index 000000000..509c28049 --- /dev/null +++ b/src/content_scripts/safari.js @@ -0,0 +1,3 @@ +import { start } from './content.js'; + +start(); diff --git a/src/content_scripts/ui/command.js b/src/content_scripts/ui/command.js index c5ba2a63e..b348a9204 100644 --- a/src/content_scripts/ui/command.js +++ b/src/content_scripts/ui/command.js @@ -1,4 +1,5 @@ import { + createElementWithContent, showBanner, showPopup, } from '../common/utils.js'; diff --git a/src/content_scripts/ui/frontend.js b/src/content_scripts/ui/frontend.js index 5eaef44d1..22272b5b2 100644 --- a/src/content_scripts/ui/frontend.js +++ b/src/content_scripts/ui/frontend.js @@ -3,6 +3,7 @@ import { createElementWithContent, generateQuickGuid, getAnnotations, + getBrowserName, getWordUnderCursor, htmlEncode, initL10n, @@ -180,6 +181,25 @@ const Front = (function() { var keystroke = document.getElementById('sk_keystroke'); var _display; + self.startInputGuard = () => { + if (getBrowserName() === "Safari") { + var inputGuard = setInterval(() => { + let input = null; + for (const a of document.querySelectorAll("input, textarea")) { + if (a.getBoundingClientRect().width) { + input = a; + break; + } + } + if (input && document.activeElement !== input) { + input.focus(); + } else { + clearInterval(inputGuard); + } + console.log(inputGuard); + }, 100); + } + }; _actions['hidePopup'] = function() { if (_display && _display.style.display !== "none") { _display.style.display = "none"; @@ -198,6 +218,7 @@ const Front = (function() { _display = td; _display.style.display = ""; _display.onShow && _display.onShow(args); + self.startInputGuard(); } function showElement(td, args) { @@ -783,6 +804,7 @@ var Find = (function() { } }; input.focus(); + Front.startInputGuard(); self.enter(); }; return self; diff --git a/src/content_scripts/ui/omnibar.js b/src/content_scripts/ui/omnibar.js index f73beab2c..e176df593 100644 --- a/src/content_scripts/ui/omnibar.js +++ b/src/content_scripts/ui/omnibar.js @@ -5,6 +5,7 @@ import { debounce } from 'lodash'; import { constructSearchURL, createElementWithContent, + getBrowserName, htmlEncode, parseAnnotation, scrollIntoViewIfNeeded, @@ -498,7 +499,7 @@ function createOmnibar(front, clipboard) { if (b.hasOwnProperty('html')) { li = self.createItemFromRawHtml(b); } else if (b.hasOwnProperty('url') && b.url !== undefined) { - if (window.navigator.userAgent.indexOf("Firefox") !== -1 && /^(place|data):/i.test(b.url)) { + if (getBrowserName() === "Firefox" && /^(place|data):/i.test(b.url)) { return null; } li = self.createURLItem(b, rxp); diff --git a/src/content_scripts/uiframe.js b/src/content_scripts/uiframe.js index 3f8f668c8..2a4151c93 100644 --- a/src/content_scripts/uiframe.js +++ b/src/content_scripts/uiframe.js @@ -1,4 +1,5 @@ import { + getBrowserName, getDocumentOrigin } from './common/utils.js'; @@ -111,6 +112,9 @@ function createUiHost(onload) { document.body.style.overflowY = _origOverflowY; } } else { + if (getBrowserName() === "Safari") { + ifr.focus(); + } if (document.body) { document.body.style.animationFillMode = "none"; if (_origOverflowY === undefined) { diff --git a/src/icons/128.png b/src/icons/128.png index ee02e80e0..b46c2e0de 100644 Binary files a/src/icons/128.png and b/src/icons/128.png differ diff --git a/src/icons/16.png b/src/icons/16.png index 450173226..52a43fc01 100644 Binary files a/src/icons/16.png and b/src/icons/16.png differ diff --git a/src/icons/48-x.png b/src/icons/48-x.png index 638e41ef7..1287befe1 100644 Binary files a/src/icons/48-x.png and b/src/icons/48-x.png differ diff --git a/src/icons/48.png b/src/icons/48.png index 5d111ecff..6230059d1 100644 Binary files a/src/icons/48.png and b/src/icons/48.png differ diff --git a/src/manifest.json b/src/manifest.json index a143dcc9e..6ef1e0029 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -79,5 +79,5 @@ "pages/shadow.css", "pages/default.css" ], - "content_security_policy": "script-src 'self' https://www.google-analytics.com chrome-extension://aajlcoiaogpknhgninhopncaldipjdnp; object-src 'self'" + "content_security_policy": "script-src 'self' chrome-extension://aajlcoiaogpknhgninhopncaldipjdnp; object-src 'self'" } diff --git a/src/pages/ga.js b/src/pages/ga.js deleted file mode 100644 index b028cf6d5..000000000 --- a/src/pages/ga.js +++ /dev/null @@ -1,6 +0,0 @@ -window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments);};ga.l=+new Date; -ga('create', 'UA-108751049-1', {'siteSpeedSampleRate': 100}); -ga('set', 'checkProtocolTask', function(){}); -ga('set', 'location', 'http://surfingkeys.com/'+document.location.pathname); -ga('set', 'dimension1', chrome.runtime.id); -ga('send', 'pageview'); diff --git a/src/pages/markdown.html b/src/pages/markdown.html index 268ad2c68..064629c0f 100644 --- a/src/pages/markdown.html +++ b/src/pages/markdown.html @@ -17,7 +17,5 @@ - - diff --git a/src/pages/options.html b/src/pages/options.html index 67a1c148b..0a171fbf2 100644 --- a/src/pages/options.html +++ b/src/pages/options.html @@ -90,12 +90,13 @@

Key mappings

- - -