aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.eslintrc1
-rw-r--r--README.md2
-rw-r--r--manifest.json3
-rw-r--r--src/actions/command.js23
-rw-r--r--src/background/histories.js84
-rw-r--r--src/console/console.scss5
6 files changed, 116 insertions, 2 deletions
diff --git a/.eslintrc b/.eslintrc
index 23f3809..8566445 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -22,6 +22,7 @@
"comma-dangle": "off",
"consistent-return": "off",
"default-case": "off",
+ "dot-location": ["error", "property"],
"function-paren-newline": "off",
"id-length": "off",
"indent": ["error", 2],
diff --git a/README.md b/README.md
index c5e2ffe..2fa4cdf 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ Firefox by WebExtensions API.
- [ ] open command
- [x] open a link
- [ ] search by keywords with engined
- - [ ] complete URLs from history
+ - [x] complete URLs from history
- [ ] complete keywords for search
- [x] tabs navigation
- [x] select a tabs by keyboard
diff --git a/manifest.json b/manifest.json
index 4e32964..dfb4ae9 100644
--- a/manifest.json
+++ b/manifest.json
@@ -16,7 +16,8 @@
},
"permissions": [
"sessions",
- "tabs"
+ "tabs",
+ "history"
],
"web_accessible_resources": [
"build/console.html"
diff --git a/src/actions/command.js b/src/actions/command.js
index 3e6eadb..3e879a6 100644
--- a/src/actions/command.js
+++ b/src/actions/command.js
@@ -1,4 +1,5 @@
import * as tabs from '../background/tabs';
+import * as histories from '../background/histories';
import * as consoleActions from './console';
const normalizeUrl = (string) => {
@@ -39,9 +40,11 @@ const bufferCommand = (keywords) => {
const doCommand = (name, remaining) => {
switch (name) {
+ case 'o':
case 'open':
// TODO use search engined and pass keywords to them
return openCommand(normalizeUrl(remaining));
+ case 't':
case 'tabopen':
return tabopenCommand(normalizeUrl(remaining));
case 'b':
@@ -53,6 +56,26 @@ const doCommand = (name, remaining) => {
const getCompletions = (command, keywords) => {
switch (command) {
+ case 'o':
+ case 'open':
+ case 't':
+ case 'tabopen':
+ return histories.getCompletions(keywords).then((pages) => {
+ let items = pages.map((page) => {
+ return {
+ caption: page.title,
+ content: page.url,
+ url: page.url
+ };
+ });
+ return [
+ {
+ name: 'History',
+ items
+ }
+ ];
+ });
+ case 'b':
case 'buffer':
return tabs.getCompletions(keywords).then((gotTabs) => {
let items = gotTabs.map((tab) => {
diff --git a/src/background/histories.js b/src/background/histories.js
new file mode 100644
index 0000000..6b6e4c6
--- /dev/null
+++ b/src/background/histories.js
@@ -0,0 +1,84 @@
+const filterHttp = (items) => {
+ const httpsHosts = items
+ .filter(item => item[1].protocol === 'https:')
+ .map(item => item[1].host);
+ const httpsHostSet = new Set(httpsHosts);
+ return items.filter(
+ item => !(item[1].protocol === 'http:' && httpsHostSet.has(item[1].host))
+ );
+};
+
+const filterEmptyTitle = (items) => {
+ return items.filter(item => item[0].title && item[0].title !== '');
+};
+
+const filterClosedPath = (items) => {
+ const allSimplePaths = items
+ .filter(item => item[1].hash === '' && item[1].search === '')
+ .map(item => item[1].origin + item[1].pathname);
+ const allSimplePathSet = new Set(allSimplePaths);
+ return items.filter(
+ item => !(item[1].hash === '' && item[1].search === '' &&
+ (/\/$/).test(item[1].pathname) &&
+ allSimplePathSet.has(
+ (item[1].origin + item[1].pathname).replace(/\/$/, '')
+ )
+ )
+ );
+};
+
+const reduceByPathname = (items, min) => {
+ let hash = {};
+ for (let item of items) {
+ let pathname = item[1].origin + item[1].pathname;
+ if (!hash[pathname]) {
+ hash[pathname] = item;
+ } else if (hash[pathname][1].href.length > item[1].href.length) {
+ hash[pathname] = item;
+ }
+ }
+ let filtered = Object.values(hash);
+ if (filtered.length < min) {
+ return items;
+ }
+ return filtered;
+};
+
+const reduceByOrigin = (items, min) => {
+ let hash = {};
+ for (let item of items) {
+ let origin = item[1].origin;
+ if (!hash[origin]) {
+ hash[origin] = item;
+ } else if (hash[origin][1].href.length > item[1].href.length) {
+ hash[origin] = item;
+ }
+ }
+ let filtered = Object.values(hash);
+ if (filtered.length < min) {
+ return items;
+ }
+ return filtered;
+};
+
+const getCompletions = (keyword) => {
+ return browser.history.search({
+ text: keyword,
+ startTime: 0,
+ }).then((historyItems) => {
+ return [historyItems.map(item => [item, new URL(item.url)])]
+ .map(filterEmptyTitle)
+ .map(filterHttp)
+ .map(filterClosedPath)
+ .map(items => reduceByPathname(items, 10))
+ .map(items => reduceByOrigin(items, 10))
+ .map(items => items
+ .sort((x, y) => x[0].visitCount < y[0].visitCount)
+ .slice(0, 10)
+ .map(item => item[0])
+ .sort((x, y) => x.url > y.url)
+ )[0];
+ });
+};
+
+export { getCompletions };
diff --git a/src/console/console.scss b/src/console/console.scss
index 7bb46dd..5823dce 100644
--- a/src/console/console.scss
+++ b/src/console/console.scss
@@ -50,11 +50,16 @@ body {
&-caption {
display: inline-block;
width: 40%;
+ text-overflow: ellipsis;
+ overflow: hidden;
}
&-url {
display: inline-block;
color: green;
+ width: 60%;
+ text-overflow: ellipsis;
+ overflow: hidden;
}
}
}