diff --git a/src/assets/.DS_Store b/src/assets/.DS_Store new file mode 100644 index 0000000..f2e1abb Binary files /dev/null and b/src/assets/.DS_Store differ diff --git a/src/assets/check.svg b/src/assets/check.svg new file mode 100644 index 0000000..9df257d --- /dev/null +++ b/src/assets/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/content.css b/src/content.css index a3f459c..457b84f 100644 --- a/src/content.css +++ b/src/content.css @@ -2,7 +2,6 @@ .omni-extension ::-webkit-scrollbar { width: 10px; height: 10px; - transition: 1s; } .omni-extension ::-webkit-scrollbar-thumb { @@ -10,13 +9,11 @@ background-clip: padding-box; border: 2px solid transparent; border-radius: 5px; - transition: 1s; } .omni-extension ::-webkit-scrollbar-thumb:vertical:hover, .omni-extension ::-webkit-scrollbar-thumb:horizontal:hover { background-color: rgb(110, 110, 110); - transition: 0.3s; } .omni-extension ::-webkit-scrollbar-track { @@ -345,10 +342,38 @@ .omni-extension .omni-keys span { margin-left: 3px; } -#body-wrap { - width: calc(100% - 350px); - overflow: auto; - position: absolute; - top: 0px; +#omni-extension-toast { + text-align: center; + font-family: Inter; + font-weight: 500; + font-size: 14px; + position: fixed; + width: fit-content; + color: var(--text); + bottom: 10px; left: 0px; + right: 0px; + margin: auto; + background: var(--background); + border-radius: 5px; + height: 40px; + line-height: 40px; + display: block; + padding-left: 10px; + padding-right: 10px; + visibility: hidden; + opacity: 0; + transition: all 0.2s cubic-bezier(0.05, 0.03, 0.35, 1); + z-index: 99999999; +} +#omni-extension-toast img { + display: inline-block; + margin-right: 5px; + vertical-align: middle; + margin-bottom: 2px; +} +.omni-show-toast { + bottom: 20px !important; + opacity: 1 !important; + visibility: visible !important; } diff --git a/src/content.html b/src/content.html index aa212d7..08c9765 100644 --- a/src/content.html +++ b/src/content.html @@ -18,5 +18,6 @@
+
The action has been successful
diff --git a/src/content.js b/src/content.js index 801cb4e..a661d52 100644 --- a/src/content.js +++ b/src/content.js @@ -14,6 +14,9 @@ $(document).ready(() => { $.get(chrome.runtime.getURL('/content.html'), (data) => { $(data).appendTo('body'); + // Get checkmark image for toast + $("#omni-extension-toast img").attr("src", chrome.runtime.getURL("assets/check.svg")); + // Request actions from the background chrome.runtime.sendMessage({request:"get-actions"}, (response) => { actions = response.actions; @@ -114,6 +117,8 @@ $(document).ready(() => { $("#omni-extension").removeClass("omni-closing"); window.setTimeout(() => { $("#omni-extension input").focus(); + focusLock.on($("#omni-extension input").get(0)); + $("#omni-extension input").focus(); }, 100); }); } @@ -134,6 +139,15 @@ $(document).ready(() => { $(this).addClass("omni-item-active"); } + // Show a toast when an action has been performed + function showToast(action) { + $("#omni-extension-toast span").html('"'+action.title+'" has been successfully performed'); + $("#omni-extension-toast").addClass("omni-show-toast"); + setTimeout(() => { + $(".omni-show-toast").removeClass("omni-show-toast"); + }, 3000) + } + // Autocomplete commands. Since they all start with different letters, it can be the default behavior function checkShortHand(e, value) { var el = $(".omni-extension input"); @@ -273,9 +287,15 @@ $(document).ready(() => { chrome.runtime.sendMessage({request:"remove", type:action.type, action:action}); } else if ($(".omni-extension input").val().toLowerCase().startsWith("/history")) { if (e.ctrlKey || e.metaKey) { - window.open($(".omni-item-active").attr("data-url"), "_self"); - } else { window.open($(".omni-item-active").attr("data-url")); + } else { + window.open($(".omni-item-active").attr("data-url"), "_self"); + } + } else if ($(".omni-extension input").val().toLowerCase().startsWith("/bookmarks")) { + if (e.ctrlKey || e.metaKey) { + window.open($(".omni-item-active").attr("data-url")); + } else { + window.open($(".omni-item-active").attr("data-url"), "_self"); } } else { chrome.runtime.sendMessage({request:action.action, tab:action, query:$(".omni-extension input").val()}); @@ -289,12 +309,13 @@ $(document).ready(() => { break; case "scroll-bottom": window.scrollTo(0,document.body.scrollHeight); + showToast(action); break; case "scroll-top": window.scrollTo(0,0); break; case "navigation": - if (e.ctrlKey) { + if (e.ctrlKey || e.metaKey) { window.open(action.url); } else { window.open(action.url, "_self"); @@ -327,6 +348,14 @@ $(document).ready(() => { case "print": window.print(); break; + case "remove-all": + case "remove-history": + case "remove-cookies": + case "remove-cache": + case "remove-local-storage": + case "remove-passwords": + showToast(action); + break; } } diff --git a/src/focus.js b/src/focus.js new file mode 100644 index 0000000..3a91606 --- /dev/null +++ b/src/focus.js @@ -0,0 +1,476 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.focusLock = factory()); +}(this, function () { 'use strict'; + + var toArray = function toArray(a) { + var ret = Array(a.length); + for (var i = 0; i < a.length; ++i) { + ret[i] = a[i]; + } + return ret; + }; + + var arrayFind = function arrayFind(array, search) { + return array.filter(function (a) { + return a === search; + })[0]; + }; + + var asArray = function asArray(a) { + return Array.isArray(a) ? a : [a]; + }; + + var tabSort = function tabSort(a, b) { + var tabDiff = a.tabIndex - b.tabIndex; + var indexDiff = a.index - b.index; + + if (tabDiff) { + if (!a.tabIndex) return 1; + if (!b.tabIndex) return -1; + } + + return tabDiff || indexDiff; + }; + + var orderByTabIndex = function orderByTabIndex(nodes, filterNegative, keepGuards) { + return toArray(nodes).map(function (node, index) { + return { + node: node, + index: index, + tabIndex: keepGuards && node.tabIndex === -1 ? (node.dataset || {}).focusGuard ? 0 : -1 : node.tabIndex + }; + }).filter(function (data) { + return !filterNegative || data.tabIndex >= 0; + }).sort(tabSort); + }; + + var tabbables = ['button:enabled:not([readonly])', 'select:enabled:not([readonly])', 'textarea:enabled:not([readonly])', 'input:enabled:not([readonly])', 'a[href]', 'area[href]', 'iframe', 'object', 'embed', '[tabindex]', '[contenteditable]', '[autofocus]']; + + var FOCUS_GROUP = 'data-focus-lock'; + var FOCUS_DISABLED = 'data-focus-lock-disabled'; + var FOCUS_ALLOW = 'data-no-focus-lock'; + var FOCUS_AUTO = 'data-autofocus-inside'; + + var queryTabbables = tabbables.join(','); + var queryGuardTabbables = queryTabbables + ', [data-focus-guard]'; + + var getFocusables = function getFocusables(parents, withGuards) { + return parents.reduce(function (acc, parent) { + return acc.concat( + // add all tabbables inside + toArray(parent.querySelectorAll(withGuards ? queryGuardTabbables : queryTabbables)), + // add if node is tabble itself + parent.parentNode ? toArray(parent.parentNode.querySelectorAll(tabbables.join(','))).filter(function (node) { + return node === parent; + }) : []); + }, []); + }; + + var getParentAutofocusables = function getParentAutofocusables(parent) { + var parentFocus = parent.querySelectorAll('[' + FOCUS_AUTO + ']'); + return toArray(parentFocus).map(function (node) { + return getFocusables([node]); + }).reduce(function (acc, nodes) { + return acc.concat(nodes); + }, []); + }; + + var isElementHidden = function isElementHidden(computedStyle) { + if (!computedStyle || !computedStyle.getPropertyValue) { + return false; + } + return computedStyle.getPropertyValue('display') === 'none' || computedStyle.getPropertyValue('visibility') === 'hidden'; + }; + + var isVisible = function isVisible(node) { + return !node || node === document || !isElementHidden(window.getComputedStyle(node, null)) && isVisible(node.parentNode); + }; + + var notHiddenInput = function notHiddenInput(node) { + return !((node.tagName === 'INPUT' || node.tagName === 'BUTTON') && (node.type === 'hidden' || node.disabled)); + }; + + var getParents = function getParents(node) { + var parents = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; + + parents.push(node); + if (node.parentNode) { + getParents(node.parentNode, parents); + } + return parents; + }; + + var getCommonParent = function getCommonParent(nodea, nodeb) { + var parentsA = getParents(nodea); + var parentsB = getParents(nodeb); + + for (var i = 0; i < parentsA.length; i += 1) { + var currentParent = parentsA[i]; + if (parentsB.indexOf(currentParent) >= 0) { + return currentParent; + } + } + return false; + }; + + var filterFocusable = function filterFocusable(nodes) { + return toArray(nodes).filter(function (node) { + return isVisible(node); + }).filter(function (node) { + return notHiddenInput(node); + }); + }; + + var getTabbableNodes = function getTabbableNodes(topNodes, withGuards) { + return orderByTabIndex(filterFocusable(getFocusables(topNodes, withGuards)), true, withGuards); + }; + + var getAllTabbableNodes = function getAllTabbableNodes(topNodes) { + return orderByTabIndex(filterFocusable(getFocusables(topNodes)), false); + }; + + var parentAutofocusables = function parentAutofocusables(topNode) { + return filterFocusable(getParentAutofocusables(topNode)); + }; + + var isRadio = function isRadio(node) { + return node.tagName === 'INPUT' && node.type === 'radio'; + }; + + var findSelectedRadio = function findSelectedRadio(node, nodes) { + return nodes.filter(isRadio).filter(function (el) { + return el.name === node.name; + }).filter(function (el) { + return el.checked; + })[0] || node; + }; + + var pickFirstFocus = function pickFirstFocus(nodes) { + if (nodes[0] && nodes.length > 1) { + if (isRadio(nodes[0]) && nodes[0].name) { + return findSelectedRadio(nodes[0], nodes); + } + } + return nodes[0]; + }; + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var filterNested = function filterNested(nodes) { + var l = nodes.length; + for (var i = 0; i < l; i += 1) { + var _loop = function _loop(j) { + if (i !== j) { + if (nodes[i].contains(nodes[j])) { + return { + v: filterNested(nodes.filter(function (x) { + return x !== nodes[j]; + })) + }; + } + } + }; + + for (var j = 0; j < l; j += 1) { + var _ret = _loop(j); + + if ((typeof _ret === 'undefined' ? 'undefined' : _typeof(_ret)) === "object") return _ret.v; + } + } + return nodes; + }; + + var getTopParent = function getTopParent(node) { + return node.parentNode ? getTopParent(node.parentNode) : node; + }; + + var getAllAffectedNodes = function getAllAffectedNodes(node) { + var nodes = asArray(node); + return nodes.filter(Boolean).reduce(function (acc, currentNode) { + var group = currentNode.getAttribute(FOCUS_GROUP); + acc.push.apply(acc, group ? filterNested(toArray(getTopParent(currentNode).querySelectorAll('[' + FOCUS_GROUP + '="' + group + '"]:not([' + FOCUS_DISABLED + '="disabled"])'))) : [currentNode]); + return acc; + }, []); + }; + + var findAutoFocused = function findAutoFocused(autoFocusables) { + return function (node) { + return !!node.autofocus || node.dataset && !!node.dataset.autofocus || autoFocusables.indexOf(node) >= 0; + }; + }; + + var newFocus = function newFocus(innerNodes, outerNodes, activeElement, lastNode, autoFocused) { + var cnt = innerNodes.length; + var firstFocus = innerNodes[0]; + var lastFocus = innerNodes[cnt - 1]; + + // focus is inside + if (innerNodes.indexOf(activeElement) >= 0) { + return undefined; + } + + var activeIndex = outerNodes.indexOf(activeElement); + var lastIndex = outerNodes.indexOf(lastNode || activeIndex); + var lastNodeInside = innerNodes.indexOf(lastNode); + var indexDiff = activeIndex - lastIndex; + var firstNodeIndex = outerNodes.indexOf(firstFocus); + var lastNodeIndex = outerNodes.indexOf(lastFocus); + + // new focus + if (activeIndex === -1 || lastNodeInside === -1) { + return innerNodes.indexOf(autoFocused.length ? pickFirstFocus(autoFocused) : pickFirstFocus(innerNodes)); + } + // old focus + if (!indexDiff && lastNodeInside >= 0) { + return lastNodeInside; + } + // jump out + if (indexDiff && Math.abs(indexDiff) > 1) { + return lastNodeInside; + } + // focus above lock + if (activeIndex <= firstNodeIndex) { + return cnt - 1; + } + // focus below lock + if (activeIndex > lastNodeIndex) { + return 0; + } + // index is inside tab order, but outside Lock + if (indexDiff) { + if (Math.abs(indexDiff) > 1) { + return lastNodeInside; + } + return (cnt + lastNodeInside + indexDiff) % cnt; + } + // do nothing + return undefined; + }; + + var getTopCommonParent = function getTopCommonParent(baseActiveElement, leftEntry, rightEntries) { + var activeElements = asArray(baseActiveElement); + var leftEntries = asArray(leftEntry); + var activeElement = activeElements[0]; + var topCommon = null; + leftEntries.filter(Boolean).forEach(function (entry) { + topCommon = getCommonParent(topCommon || entry, entry) || topCommon; + rightEntries.filter(Boolean).forEach(function (subEntry) { + var common = getCommonParent(activeElement, subEntry); + if (common) { + if (!topCommon || common.contains(topCommon)) { + topCommon = common; + } else { + topCommon = getCommonParent(common, topCommon); + } + } + }); + }); + return topCommon; + }; + + var allParentAutofocusables = function allParentAutofocusables(entries) { + return entries.reduce(function (acc, node) { + return acc.concat(parentAutofocusables(node)); + }, []); + }; + + var notAGuard = function notAGuard(node) { + return !(node.dataset && node.dataset.focusGuard); + }; + + var reorderNodes = function reorderNodes(srcNodes, dstNodes) { + return srcNodes.map(function (dnode) { + return dstNodes.find(function (_ref) { + var node = _ref.node; + return dnode === node; + }); + }).filter(Boolean); + }; + + var getFocusMerge = function getFocusMerge(topNode, lastNode) { + var activeElement = document && document.activeElement; + var entries = getAllAffectedNodes(topNode).filter(notAGuard); + + var commonParent = getTopCommonParent(activeElement || topNode, topNode, entries); + + var innerElements = getTabbableNodes(entries).filter(function (_ref5) { + var node = _ref5.node; + return notAGuard(node); + }); + + if (!innerElements[0]) { + innerElements = getAllTabbableNodes(entries).filter(function (_ref6) { + var node = _ref6.node; + return notAGuard(node); + }); + if (!innerElements[0]) { + return undefined; + } + } + + var outerNodes = getTabbableNodes([commonParent]).map(function (_ref7) { + var node = _ref7.node; + return node; + }); + var orderedInnerElements = reorderNodes(outerNodes, innerElements); + var innerNodes = orderedInnerElements.map(function (_ref8) { + var node = _ref8.node; + return node; + }); + + var newId = newFocus(innerNodes, outerNodes, activeElement, lastNode, innerNodes.filter(findAutoFocused(allParentAutofocusables(entries)))); + + if (newId === undefined) { + return newId; + } + return orderedInnerElements[newId]; + }; + + var focusInFrame = function focusInFrame(frame) { + return frame === document.activeElement; + }; + + var focusInsideIframe = function focusInsideIframe(topNode) { + return !!arrayFind(toArray(topNode.querySelectorAll('iframe')), focusInFrame); + }; + + var focusInside = function focusInside(topNode) { + var activeElement = document && document.activeElement; + + if (!activeElement || activeElement.dataset && activeElement.dataset.focusGuard) { + return false; + } + return getAllAffectedNodes(topNode).reduce(function (result, node) { + return result || node.contains(activeElement) || focusInsideIframe(node); + }, false); + }; + + var focusIsHidden = function focusIsHidden() { + return document && toArray(document.querySelectorAll('[' + FOCUS_ALLOW + ']')).some(function (node) { + return node.contains(document.activeElement); + }); + }; + + var focusOn = function focusOn(target) { + target.focus(); + if (target.contentWindow) { + target.contentWindow.focus(); + } + }; + + var guardCount = 0; + var lockDisabled = false; + + var setFocus = (function (topNode, lastNode) { + var focusable = getFocusMerge(topNode, lastNode); + + if (lockDisabled) { + return; + } + + if (focusable) { + if (guardCount > 2) { + // eslint-disable-next-line no-console + console.error('FocusLock: focus-fighting detected. Only one focus management system could be active. ' + 'See https://github.com/theKashey/focus-lock/#focus-fighting'); + lockDisabled = true; + setTimeout(function () { + lockDisabled = false; + }, 1); + return; + } + guardCount++; + focusOn(focusable.node); + guardCount--; + } + }); + + var lastActiveTrap = 0; + var lastActiveFocus = null; + + var focusOnBody = function focusOnBody() { + return document && document.activeElement === document.body; + }; + + var isFreeFocus = function isFreeFocus() { + return focusOnBody() || focusIsHidden(); + }; + + var activateTrap = function activateTrap() { + var result = false; + + if (lastActiveTrap) { + var observed = lastActiveTrap; + + if (!isFreeFocus()) { + if (observed && !focusInside(observed)) { + result = setFocus(observed, lastActiveFocus); + } + + lastActiveFocus = document.activeElement; + } + } + + return result; + }; + + var reducePropsToState = function reducePropsToState(propsList) { + return propsList.filter(function (node) { + return node; + }).slice(-1)[0]; + }; + + var handleStateChangeOnClient = function handleStateChangeOnClient(trap) { + lastActiveTrap = trap; + + if (trap) { + activateTrap(); + } + }; + + var instances = []; + + var emitChange = function emitChange(event) { + if (handleStateChangeOnClient(reducePropsToState(instances))) { + event && event.preventDefault(); + return true; + } + + return false; + }; + + var attachHandler = function attachHandler() { + document.addEventListener('focusin', emitChange); + }; + + var detachHandler = function detachHandler() { + document.removeEventListener('focusin', emitChange); + }; + + var focusLock = { + on: function on(domNode) { + if (instances.length === 0) { + attachHandler(); + } + + if (instances.indexOf(domNode) < 0) { + instances.push(domNode); + emitChange(); + } + }, + off: function off(domNode) { + instances = instances.filter(function (node) { + return node !== domNode; + }); + emitChange(); + + if (instances.length === 0) { + detachHandler(); + } + } + }; + + return focusLock; + +})); diff --git a/src/manifest.json b/src/manifest.json index d232525..f6336f0 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -2,7 +2,7 @@ "name": "Omni", "description": "Supercharge Chrome with commands, shortcuts, and more", "offline_enabled": true, - "version": "1.4.1", + "version": "1.4.2", "manifest_version": 3, "action": { "icons": { @@ -34,7 +34,7 @@ "" ], "run_at": "document_end", - "js": ["jquery.js", "content.js"], + "js": ["focus.js", "jquery.js", "content.js"], "css": ["content.css"] } ],