Merge pull request 'v0.3: Extension' (#3) from develop into master
Reviewed-on: #3
This commit is contained in:
commit
d0092657d7
10
.drone.yml
10
.drone.yml
@ -7,8 +7,12 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- npm install
|
- npm install
|
||||||
- npm run buildProd
|
- npm run buildProd
|
||||||
- mkdir build
|
- npm run ext:sign
|
||||||
- tar -czvf build/bundle.tar.gz dist
|
environment:
|
||||||
|
WEB_EXT_API_KEY:
|
||||||
|
from_secret: WEB_EXT_API_KEY
|
||||||
|
WEB_EXT_API_SECRET:
|
||||||
|
from_secret: WEB_EXT_API_SECRET
|
||||||
|
|
||||||
- name: gitea_release
|
- name: gitea_release
|
||||||
image: plugins/gitea-release
|
image: plugins/gitea-release
|
||||||
@ -17,7 +21,7 @@ steps:
|
|||||||
from_secret: GITEA_API_KEY
|
from_secret: GITEA_API_KEY
|
||||||
base_url: https://git.kapelle.org
|
base_url: https://git.kapelle.org
|
||||||
files:
|
files:
|
||||||
- build/*
|
- web-ext-artifacts/*
|
||||||
checksum:
|
checksum:
|
||||||
- md5
|
- md5
|
||||||
- sha1
|
- sha1
|
||||||
|
@ -18,5 +18,8 @@
|
|||||||
"ecmaFeatures": {
|
"ecmaFeatures": {
|
||||||
"jsx": true
|
"jsx": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"VERSION": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
32
.vscode/tasks.json
vendored
32
.vscode/tasks.json
vendored
@ -15,6 +15,20 @@
|
|||||||
"group": "none",
|
"group": "none",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"label": "watch",
|
||||||
|
"command": "npm",
|
||||||
|
"type": "shell",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"watch"
|
||||||
|
],
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always"
|
||||||
|
},
|
||||||
|
"group": "none",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"label": "Build prod",
|
"label": "Build prod",
|
||||||
"command": "npm",
|
"command": "npm",
|
||||||
@ -26,7 +40,7 @@
|
|||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always"
|
"reveal": "always"
|
||||||
},
|
},
|
||||||
"group": "none",
|
"group": "build",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -40,7 +54,21 @@
|
|||||||
"presentation": {
|
"presentation": {
|
||||||
"reveal": "always"
|
"reveal": "always"
|
||||||
},
|
},
|
||||||
"group": "none",
|
"group": "build",
|
||||||
|
"problemMatcher": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "ext:run",
|
||||||
|
"command": "npm",
|
||||||
|
"type": "shell",
|
||||||
|
"args": [
|
||||||
|
"run",
|
||||||
|
"ext:run"
|
||||||
|
],
|
||||||
|
"presentation": {
|
||||||
|
"reveal": "always"
|
||||||
|
},
|
||||||
|
"group": "build",
|
||||||
"problemMatcher": []
|
"problemMatcher": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
42
README.md
42
README.md
@ -1,33 +1,19 @@
|
|||||||
[![Build Status](https://drone.srv.kapelle.org/api/badges/niklas/startpage/status.svg?ref=refs/heads/master)](https://drone.srv.kapelle.org/niklas/startpage)
|
# How to install
|
||||||
|
|
||||||
# How to set the new Tab
|
1. Download the extension
|
||||||
## Firefox
|
2. Goto `about:addons`
|
||||||
|
3. Click on the top right button
|
||||||
|
4. Click `Install Add-on From File`
|
||||||
|
5. Select the Add-on
|
||||||
|
|
||||||
[Reddit thread](https://www.reddit.com/r/startpages/comments/g3qndt/psa_how_to_set_a_custom_new_tab_page_in_firefox/)
|
# How to build
|
||||||
|
|
||||||
Create 2 files inside of the firefox install directory.
|
1. Build the bundle with `npm run buildProd`
|
||||||
|
2. Provide the secrets required for signing the addon
|
||||||
|
3. Sign the Add-on by Mozilla with `npm run ext:sign`
|
||||||
|
|
||||||
`${installDir}/defaults/pref/autoconfig.js`
|
# Usefull links
|
||||||
```js
|
|
||||||
pref("general.config.filename", "autoconfig.cfg");
|
|
||||||
pref("general.config.obscure_value", 0);
|
|
||||||
pref("general.config.sandbox_enabled", false);
|
|
||||||
```
|
|
||||||
|
|
||||||
`${installDir}/autoconfig.cfg`
|
- [Mozilla Developer hub](https://addons.mozilla.org/en-US/developers/)
|
||||||
```
|
- [Mozilla Extension documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions)
|
||||||
// first line is a comment
|
- [Web ext documentation](https://extensionworkshop.com/documentation/develop/web-ext-command-reference/)
|
||||||
var {classes:Cc,interfaces:Ci,utils:Cu} = Components;
|
|
||||||
var newTabURL = "http://localhost:8080";
|
|
||||||
aboutNewTabService = Cc["@mozilla.org/browser/aboutnewtab-service;1"].getService(Ci.nsIAboutNewTabService);
|
|
||||||
aboutNewTabService.newTabURL = newTabURL;
|
|
||||||
```
|
|
||||||
|
|
||||||
Replace the "localhost" line in `autoconfig.cfg` with your URL.
|
|
||||||
Also you can set the homepage via your normal settings.
|
|
||||||
|
|
||||||
Alternatively you can use a extentions called [New Tab override](https://addons.mozilla.org/en-US/firefox/addon/new-tab-override/).
|
|
||||||
|
|
||||||
## Chrome
|
|
||||||
|
|
||||||
Maybe [this](https://chrome.google.com/webstore/detail/custom-new-tab-url/mmjbdbjnoablegbkcklggeknkfcjkjia) extention. Not tested yet.
|
|
4128
package-lock.json
generated
4128
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -6,7 +6,11 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"devServer": "webpack-dev-server --mode development --open",
|
"devServer": "webpack-dev-server --mode development --open",
|
||||||
"build": "webpack --mode development",
|
"build": "webpack --mode development",
|
||||||
"buildProd": "webpack --mode production"
|
"buildProd": "webpack --mode production",
|
||||||
|
"watch": "webpack --watch --mode development",
|
||||||
|
"ext:build": "web-ext build --source-dir dist --overwrite-dest",
|
||||||
|
"ext:sign": "web-ext sign --source-dir dist --channel unlisted",
|
||||||
|
"ext:run": "web-ext run --source-dir dist --no-reload"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
@ -16,6 +20,7 @@
|
|||||||
"@types/react-dom": "^16.9.6",
|
"@types/react-dom": "^16.9.6",
|
||||||
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
"@typescript-eslint/eslint-plugin": "^2.28.0",
|
||||||
"@typescript-eslint/parser": "^2.28.0",
|
"@typescript-eslint/parser": "^2.28.0",
|
||||||
|
"copy-webpack-plugin": "^5.1.1",
|
||||||
"css-loader": "^3.5.2",
|
"css-loader": "^3.5.2",
|
||||||
"eslint": "^6.8.0",
|
"eslint": "^6.8.0",
|
||||||
"eslint-plugin-react": "^7.19.0",
|
"eslint-plugin-react": "^7.19.0",
|
||||||
@ -26,6 +31,8 @@
|
|||||||
"sass-loader": "^8.0.2",
|
"sass-loader": "^8.0.2",
|
||||||
"ts-loader": "^7.0.0",
|
"ts-loader": "^7.0.0",
|
||||||
"typescript": "^3.8.3",
|
"typescript": "^3.8.3",
|
||||||
|
"web-ext": "^4.2.0",
|
||||||
|
"web-ext-types": "^3.2.1",
|
||||||
"webpack": "^4.42.1",
|
"webpack": "^4.42.1",
|
||||||
"webpack-cli": "^3.3.11",
|
"webpack-cli": "^3.3.11",
|
||||||
"webpack-dev-server": "^3.10.3"
|
"webpack-dev-server": "^3.10.3"
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import Quick from "./Quick";
|
import Quick from "./Quick";
|
||||||
import QuickItem from "./QuickItem";
|
|
||||||
import QuickCategory from "./QuickCategory";
|
|
||||||
import Clock from "./Clock";
|
import Clock from "./Clock";
|
||||||
import Search from "./Search";
|
import Search from "./Search";
|
||||||
|
|
||||||
@ -14,25 +12,9 @@ class App extends React.Component {
|
|||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return <div>
|
return <div>
|
||||||
<div className="center-wrapper">
|
<div className="center-wrapper">
|
||||||
<Clock/>
|
<Clock />
|
||||||
<Search/>
|
<Search />
|
||||||
<Quick>
|
<Quick />
|
||||||
<QuickCategory name="Productivity">
|
|
||||||
<QuickItem name="Nextcloud" url="https://nc.kapelle.org"/>
|
|
||||||
<QuickItem name="Git" url="https://git.kapelle.org"/>
|
|
||||||
<QuickItem name="GitHub" url="https://github.com/"/>
|
|
||||||
</QuickCategory>
|
|
||||||
|
|
||||||
<QuickCategory name="Personal">
|
|
||||||
<QuickItem name="Youtube" url="https://youtube.com/"/>
|
|
||||||
<QuickItem name="Reddit" url="https://reddit.com/"/>
|
|
||||||
<QuickItem name="Netflix" url="https://netflix.com/"/>
|
|
||||||
</QuickCategory>
|
|
||||||
|
|
||||||
<QuickCategory name="Other">
|
|
||||||
<QuickItem name="Bitwarden" url="https://vault.kapelle.org"/>
|
|
||||||
</QuickCategory>
|
|
||||||
</Quick>
|
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -45,12 +45,12 @@ class Clock extends React.Component<{}, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startTimer() {
|
startTimer() {
|
||||||
this.interval = setInterval(()=>{
|
this.interval = setInterval(() => {
|
||||||
this.updateTime();
|
this.updateTime();
|
||||||
|
|
||||||
// This will change only if the tab stays open after midnight. But still, lets cover that
|
// This will change only if the tab stays open after midnight. But still, lets cover that
|
||||||
this.updateDate();
|
this.updateDate();
|
||||||
},1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -59,7 +59,7 @@ class Clock extends React.Component<{}, State> {
|
|||||||
this.startTimer();
|
this.startTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount(){
|
componentWillUnmount() {
|
||||||
clearInterval(this.interval);
|
clearInterval(this.interval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,43 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import "../style/quick.scss";
|
import "../style/quick.scss";
|
||||||
|
import { Bookmarks } from "../types/Bookmarks";
|
||||||
|
import QuickCategory from "./QuickCategory";
|
||||||
|
import getBookmarks from "../functions/getBookmarks";
|
||||||
|
|
||||||
interface Props {
|
interface State {
|
||||||
children: React.ReactNode;
|
bookmarks: Bookmarks;
|
||||||
}
|
}
|
||||||
|
|
||||||
class Quick extends React.Component<Props> {
|
class Quick extends React.Component<{}, State> {
|
||||||
|
|
||||||
constructor(props){
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
bookmarks: {
|
||||||
|
folder: [],
|
||||||
|
orphans: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBookmarks(): Promise<void> {
|
||||||
|
const bookmarks = await getBookmarks();
|
||||||
|
this.setState({
|
||||||
|
bookmarks: bookmarks
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.getBookmarks();
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return <div className="quick-component">
|
return <div className="quick-component">
|
||||||
<div className="container">
|
<div className="container">
|
||||||
{this.props.children}
|
{this.state.bookmarks.folder.map((e, i) => {
|
||||||
|
return <QuickCategory folder={e} key={i} />;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import "../style/quickCategory.scss";
|
import "../style/quickCategory.scss";
|
||||||
|
import { BookmarkFolder } from "../types/Bookmarks";
|
||||||
|
import QuickItem from "./QuickItem";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode;
|
folder: BookmarkFolder;
|
||||||
name: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class QuickCategory extends React.Component<Props> {
|
class QuickCategory extends React.Component<Props> {
|
||||||
|
|
||||||
constructor(props){
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return <div className="quickCategory-component">
|
return <div className="quickCategory-component">
|
||||||
<div className="title">
|
<div className="title">
|
||||||
{this.props.name}
|
{this.props.folder.name}
|
||||||
</div>
|
</div>
|
||||||
<div className="children">
|
<div className="children">
|
||||||
{this.props.children}
|
{this.props.folder.bookmarks.map((e, i) => {
|
||||||
|
return <QuickItem bookmark={e} key={i} />;
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import "../style/quickItem.scss";
|
import "../style/quickItem.scss";
|
||||||
|
import { Bookmark } from "../types/Bookmarks";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
name: string;
|
bookmark: Bookmark;
|
||||||
url: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class QuickItem extends React.Component<Props> {
|
class QuickItem extends React.Component<Props> {
|
||||||
@ -14,12 +14,12 @@ class QuickItem extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onClick() {
|
onClick() {
|
||||||
window.location.href = this.props.url;
|
window.location.href = this.props.bookmark.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(): JSX.Element {
|
render(): JSX.Element {
|
||||||
return <div className="quickItem-component" onClick={() => this.onClick()}>
|
return <div className="quickItem-component" onClick={() => this.onClick()}>
|
||||||
{this.props.name}
|
{this.props.bookmark.name}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,30 +1,80 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
import "../style/search.scss";
|
import "../style/search.scss";
|
||||||
|
import Sugestion from "./Sugestion";
|
||||||
|
import { Suggestion, SuggestionType } from "../types/suggestion";
|
||||||
|
import getGoogleSuggestions from "../functions/getGoogleSuggestions";
|
||||||
|
|
||||||
class Search extends React.Component {
|
interface State {
|
||||||
|
sugestions: Suggestion[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class Search extends React.Component<{}, State> {
|
||||||
|
|
||||||
private searchInput = React.createRef<HTMLInputElement>()
|
private searchInput = React.createRef<HTMLInputElement>()
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
sugestions: []
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQuery(query: string) {
|
handleQuery(query: string) {
|
||||||
this.search(query);
|
this.search(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
search(query: string){
|
search(query: string) {
|
||||||
const url = new URL("https://duckduckgo.com/");
|
const url = new URL("https://duckduckgo.com/");
|
||||||
const param = new URLSearchParams();
|
const param = new URLSearchParams();
|
||||||
param.append("q",query);
|
param.append("q", query);
|
||||||
url.search = param.toString();
|
url.search = param.toString();
|
||||||
window.location.href = url.toString();
|
window.location.href = url.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
onKeyDown(event: React.KeyboardEvent) {
|
async sugest(input: string) {
|
||||||
|
if (input === "") {
|
||||||
|
this.setState({
|
||||||
|
sugestions: []
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const results = await getGoogleSuggestions(input);
|
||||||
|
|
||||||
|
const newSuggestionState: Suggestion[] = results.suggestions.map((e) => {
|
||||||
|
return {
|
||||||
|
display: e,
|
||||||
|
type: SuggestionType.QUERY
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
sugestions: newSuggestionState
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === 'Enter') {
|
||||||
this.handleQuery(this.searchInput.current.value);
|
this.handleQuery(this.searchInput.current.value);
|
||||||
|
} else if (event.key === "ArrowDown") {
|
||||||
|
console.log("down");
|
||||||
|
} else if (event.key === "ArrowUp") {
|
||||||
|
console.log("up");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange() {
|
||||||
|
this.sugest(this.searchInput.current.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
onSugestionClick(e: Suggestion) {
|
||||||
|
if (e.type === SuggestionType.QUERY) {
|
||||||
|
this.search(e.display);
|
||||||
|
} else {
|
||||||
|
window.location.href = e.url;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,9 +82,21 @@ class Search extends React.Component {
|
|||||||
return <div className="search-component" >
|
return <div className="search-component" >
|
||||||
<input type="text"
|
<input type="text"
|
||||||
autoFocus
|
autoFocus
|
||||||
onKeyDown={(e)=>this.onKeyDown(e)}
|
onKeyDown={(e) => this.onKeyDown(e)}
|
||||||
|
onChange={() => this.onChange()}
|
||||||
ref={this.searchInput}
|
ref={this.searchInput}
|
||||||
|
autoComplete="off"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<div className="suggestion-area">
|
||||||
|
{this.state.sugestions.map((e) => {
|
||||||
|
return <Sugestion
|
||||||
|
key={e.display}
|
||||||
|
suggestion={e}
|
||||||
|
onClick={() => { this.onSugestionClick(e); }}
|
||||||
|
/>;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
src/components/Sugestion.tsx
Normal file
17
src/components/Sugestion.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import "../style/suggestion.scss";
|
||||||
|
import { Suggestion } from "../types/suggestion";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
suggestion: Suggestion;
|
||||||
|
onClick: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Sugestion = (props: Props) => {
|
||||||
|
return <div className="suggestion-component" onClick={props.onClick}>
|
||||||
|
{props.suggestion.display}
|
||||||
|
</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Sugestion;
|
43
src/functions/getBookmarks.ts
Normal file
43
src/functions/getBookmarks.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { Bookmarks, Bookmark } from "../types/Bookmarks";
|
||||||
|
|
||||||
|
function createBookmark(from: browser.bookmarks.BookmarkTreeNode): Bookmark {
|
||||||
|
return {
|
||||||
|
name: from.title,
|
||||||
|
url: from.url
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async (): Promise<Bookmarks> => {
|
||||||
|
const treeNode = await browser.bookmarks.getTree();
|
||||||
|
|
||||||
|
const root: Bookmarks = {
|
||||||
|
folder: [],
|
||||||
|
orphans: []
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootNode = treeNode[0].children.find((e)=>e.id === "menu________");
|
||||||
|
|
||||||
|
// Set all top level bookmarks (without folder)
|
||||||
|
root.orphans = rootNode.children
|
||||||
|
.filter((e)=> e.type === "bookmark" )
|
||||||
|
.sort((a,b)=> a.index - b.index )
|
||||||
|
.map(createBookmark);
|
||||||
|
|
||||||
|
// Get all top level folders
|
||||||
|
root.folder = rootNode.children
|
||||||
|
.filter((e)=> e.type === "folder")
|
||||||
|
.filter((e)=> e.children.length > 0)
|
||||||
|
.sort((a,b)=> a.index - b.index )
|
||||||
|
.map((e)=>{
|
||||||
|
const children: Bookmark[] = e.children
|
||||||
|
.filter((e)=> e.type === "bookmark")
|
||||||
|
.sort((a,b)=> a.index - b.index )
|
||||||
|
.map(createBookmark);
|
||||||
|
return {
|
||||||
|
name: e.title,
|
||||||
|
bookmarks: children
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return root;
|
||||||
|
};
|
17
src/functions/getGoogleSuggestions.ts
Normal file
17
src/functions/getGoogleSuggestions.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import GoogleSuggestions from "../types/GoogleSuggestions";
|
||||||
|
|
||||||
|
export default async (query: string): Promise<GoogleSuggestions> => {
|
||||||
|
const url = new URL(" http://suggestqueries.google.com/complete/search");
|
||||||
|
const param = new URLSearchParams();
|
||||||
|
param.append("q", query);
|
||||||
|
param.append("client", "firefox"); // TODO check if the result are different on different clients
|
||||||
|
url.search = param.toString();
|
||||||
|
|
||||||
|
const response = await fetch(url.toString());
|
||||||
|
const results = await response.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
query: results[0],
|
||||||
|
suggestions: results[1]
|
||||||
|
};
|
||||||
|
};
|
25
src/manifest.json
Normal file
25
src/manifest.json
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Startpage",
|
||||||
|
"version": "$VERSION",
|
||||||
|
"description": "Startpage",
|
||||||
|
"developer": {
|
||||||
|
"name": "Djeeberjr"
|
||||||
|
},
|
||||||
|
"chrome_url_overrides": {
|
||||||
|
"newtab": "startpage/index.html"
|
||||||
|
},
|
||||||
|
"chrome_settings_overrides": {
|
||||||
|
"homepage": "startpage/index.html"
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"<all_urls>",
|
||||||
|
"bookmarks"
|
||||||
|
],
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"update_url": "https://cocainum.srv.kapelle.org/api/service/staticRedirect/staticRedirect/startpageExtUpdate",
|
||||||
|
"id": "{9c6ffd7a-0e0b-4276-8208-aa72030477ce}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,19 @@
|
|||||||
.search-component{
|
.search-component{
|
||||||
|
|
||||||
text-align: center;
|
margin: 2em auto 2em auto;
|
||||||
margin-top: 2em;
|
width: 50%;
|
||||||
margin-bottom: 2em;
|
position: relative;
|
||||||
|
|
||||||
input[type=text]{
|
input[type=text]{
|
||||||
width: 40%;
|
width: 100%;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0.5em 1.5em;
|
padding: 0.5em 1em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suggestion-area{
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
left: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
12
src/style/suggestion.scss
Normal file
12
src/style/suggestion.scss
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.suggestion-component{
|
||||||
|
padding: 0.2em 1em;
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
border: 1px solid #d4d4d4;
|
||||||
|
border-top: none;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #d4d4d4;
|
||||||
|
}
|
||||||
|
}
|
16
src/types/Bookmarks.ts
Normal file
16
src/types/Bookmarks.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
interface Bookmarks {
|
||||||
|
folder: BookmarkFolder[];
|
||||||
|
orphans: Bookmark[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BookmarkFolder {
|
||||||
|
name: string;
|
||||||
|
bookmarks: Bookmark[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Bookmark {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Bookmarks, Bookmark, BookmarkFolder };
|
31
src/types/GoogleSuggestions.ts
Normal file
31
src/types/GoogleSuggestions.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Example if client == chrome:
|
||||||
|
[
|
||||||
|
"hello w",
|
||||||
|
["hello world","hello world anime","hello world java","hello world c","hello world python","hello world html","hello world mhw","hello welcome home"],
|
||||||
|
["","","","","","","",""],
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
"google:clientdata":{"bpc":false,"tlw":false},
|
||||||
|
"google:suggestrelevance":[1250,1050,850,700,601,600,551,550],
|
||||||
|
"google:suggesttype":["QUERY","QUERY","QUERY","QUERY","QUERY","QUERY","QUERY","QUERY"],
|
||||||
|
"google:verbatimrelevance":851
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Example if client == firefox:
|
||||||
|
[
|
||||||
|
"hello wo",
|
||||||
|
[
|
||||||
|
"hello world","hello world anime","hello world java","hello world c","hello world python","hello world html",
|
||||||
|
"hello world mhw","hello world anime stream","hello world programm","hello world anime ger sub"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface GoogleSuggestions {
|
||||||
|
query: string;
|
||||||
|
suggestions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GoogleSuggestions;
|
12
src/types/Suggestion.ts
Normal file
12
src/types/Suggestion.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
enum SuggestionType {
|
||||||
|
QUICK,
|
||||||
|
QUERY
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Suggestion {
|
||||||
|
display: string;
|
||||||
|
type: SuggestionType;
|
||||||
|
url?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { SuggestionType, Suggestion };
|
@ -6,6 +6,7 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"noLib": false,
|
"noLib": false,
|
||||||
"jsx": "react",
|
"jsx": "react",
|
||||||
|
"typeRoots": ["node_modules/@types", "node_modules/web-ext-types"],
|
||||||
},
|
},
|
||||||
"exclude": [
|
"exclude": [
|
||||||
"node_modules"
|
"node_modules"
|
||||||
|
@ -3,10 +3,19 @@
|
|||||||
|
|
||||||
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
const HtmlWebpackPlugin = require("html-webpack-plugin");
|
||||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||||
|
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||||
|
const Webpack = require("webpack");
|
||||||
|
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
|
|
||||||
const DEVELOPMENT = process.env.NODE_ENV === "development";
|
const DEVELOPMENT = process.env.NODE_ENV === "development";
|
||||||
|
|
||||||
|
const version = require('child_process')
|
||||||
|
.execSync("git describe --abbrev=0")
|
||||||
|
.toString()
|
||||||
|
.replace("v", "")
|
||||||
|
.trim();
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
context: path.join(__dirname, "src"),
|
context: path.join(__dirname, "src"),
|
||||||
resolve: {
|
resolve: {
|
||||||
@ -14,7 +23,7 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
entry: ["./index.tsx"],
|
entry: ["./index.tsx"],
|
||||||
output: {
|
output: {
|
||||||
path: path.join(__dirname, "dist"),
|
path: path.join(__dirname, "dist/startpage"),
|
||||||
filename: "bundle.js"
|
filename: "bundle.js"
|
||||||
},
|
},
|
||||||
devtool: DEVELOPMENT ? "source-map" : false,
|
devtool: DEVELOPMENT ? "source-map" : false,
|
||||||
@ -27,6 +36,9 @@ module.exports = {
|
|||||||
liveReload: true,
|
liveReload: true,
|
||||||
watchContentBase: true
|
watchContentBase: true
|
||||||
},
|
},
|
||||||
|
watchOptions: {
|
||||||
|
ignored: ['dist/**', 'node_modules/**']
|
||||||
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
@ -58,6 +70,18 @@ module.exports = {
|
|||||||
hash: true,
|
hash: true,
|
||||||
minify: !DEVELOPMENT
|
minify: !DEVELOPMENT
|
||||||
}),
|
}),
|
||||||
new MiniCssExtractPlugin()
|
new MiniCssExtractPlugin(),
|
||||||
|
new CopyWebpackPlugin([
|
||||||
|
{
|
||||||
|
from: "manifest.json",
|
||||||
|
to: "..",
|
||||||
|
transform(content) {
|
||||||
|
return content.toString().replace("$VERSION", version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
new Webpack.DefinePlugin({
|
||||||
|
VERSION: JSON.stringify(version),
|
||||||
|
})
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user