Merge branch 'history-completion'
This commit is contained in:
commit
8a8222158c
6 changed files with 116 additions and 2 deletions
|
@ -22,6 +22,7 @@
|
||||||
"comma-dangle": "off",
|
"comma-dangle": "off",
|
||||||
"consistent-return": "off",
|
"consistent-return": "off",
|
||||||
"default-case": "off",
|
"default-case": "off",
|
||||||
|
"dot-location": ["error", "property"],
|
||||||
"function-paren-newline": "off",
|
"function-paren-newline": "off",
|
||||||
"id-length": "off",
|
"id-length": "off",
|
||||||
"indent": ["error", 2],
|
"indent": ["error", 2],
|
||||||
|
|
|
@ -21,7 +21,7 @@ Firefox by WebExtensions API.
|
||||||
- [ ] open command
|
- [ ] open command
|
||||||
- [x] open a link
|
- [x] open a link
|
||||||
- [ ] search by keywords with engined
|
- [ ] search by keywords with engined
|
||||||
- [ ] complete URLs from history
|
- [x] complete URLs from history
|
||||||
- [ ] complete keywords for search
|
- [ ] complete keywords for search
|
||||||
- [x] tabs navigation
|
- [x] tabs navigation
|
||||||
- [x] select a tabs by keyboard
|
- [x] select a tabs by keyboard
|
||||||
|
|
|
@ -16,7 +16,8 @@
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"sessions",
|
"sessions",
|
||||||
"tabs"
|
"tabs",
|
||||||
|
"history"
|
||||||
],
|
],
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
"build/console.html"
|
"build/console.html"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import * as tabs from '../background/tabs';
|
import * as tabs from '../background/tabs';
|
||||||
|
import * as histories from '../background/histories';
|
||||||
import * as consoleActions from './console';
|
import * as consoleActions from './console';
|
||||||
|
|
||||||
const normalizeUrl = (string) => {
|
const normalizeUrl = (string) => {
|
||||||
|
@ -39,9 +40,11 @@ const bufferCommand = (keywords) => {
|
||||||
|
|
||||||
const doCommand = (name, remaining) => {
|
const doCommand = (name, remaining) => {
|
||||||
switch (name) {
|
switch (name) {
|
||||||
|
case 'o':
|
||||||
case 'open':
|
case 'open':
|
||||||
// TODO use search engined and pass keywords to them
|
// TODO use search engined and pass keywords to them
|
||||||
return openCommand(normalizeUrl(remaining));
|
return openCommand(normalizeUrl(remaining));
|
||||||
|
case 't':
|
||||||
case 'tabopen':
|
case 'tabopen':
|
||||||
return tabopenCommand(normalizeUrl(remaining));
|
return tabopenCommand(normalizeUrl(remaining));
|
||||||
case 'b':
|
case 'b':
|
||||||
|
@ -53,6 +56,26 @@ const doCommand = (name, remaining) => {
|
||||||
|
|
||||||
const getCompletions = (command, keywords) => {
|
const getCompletions = (command, keywords) => {
|
||||||
switch (command) {
|
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':
|
case 'buffer':
|
||||||
return tabs.getCompletions(keywords).then((gotTabs) => {
|
return tabs.getCompletions(keywords).then((gotTabs) => {
|
||||||
let items = gotTabs.map((tab) => {
|
let items = gotTabs.map((tab) => {
|
||||||
|
|
84
src/background/histories.js
Normal file
84
src/background/histories.js
Normal file
|
@ -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 };
|
|
@ -50,11 +50,16 @@ body {
|
||||||
&-caption {
|
&-caption {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 40%;
|
width: 40%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
&-url {
|
&-url {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
color: green;
|
color: green;
|
||||||
|
width: 60%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue