Compare commits

..

No commits in common. "master" and "v0.2" have entirely different histories.
master ... v0.2

23 changed files with 92 additions and 4535 deletions

View File

@ -7,12 +7,8 @@ steps:
commands: commands:
- npm install - npm install
- npm run buildProd - npm run buildProd
- npm run ext:sign - mkdir build
environment: - tar -czvf build/bundle.tar.gz dist
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
@ -21,7 +17,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:
- web-ext-artifacts/* - build/*
checksum: checksum:
- md5 - md5
- sha1 - sha1

View File

@ -18,8 +18,5 @@
"ecmaFeatures": { "ecmaFeatures": {
"jsx": true "jsx": true
} }
}, }
"globals": {
"VERSION": true
}
} }

32
.vscode/tasks.json vendored
View File

@ -15,20 +15,6 @@
"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",
@ -40,7 +26,7 @@
"presentation": { "presentation": {
"reveal": "always" "reveal": "always"
}, },
"group": "build", "group": "none",
"problemMatcher": [] "problemMatcher": []
}, },
{ {
@ -54,21 +40,7 @@
"presentation": { "presentation": {
"reveal": "always" "reveal": "always"
}, },
"group": "build", "group": "none",
"problemMatcher": []
},
{
"label": "ext:run",
"command": "npm",
"type": "shell",
"args": [
"run",
"ext:run"
],
"presentation": {
"reveal": "always"
},
"group": "build",
"problemMatcher": [] "problemMatcher": []
} }
] ]

View File

@ -1,19 +1,33 @@
# How to install [![Build Status](https://drone.srv.kapelle.org/api/badges/niklas/startpage/status.svg?ref=refs/heads/master)](https://drone.srv.kapelle.org/niklas/startpage)
1. Download the extension # How to set the new Tab
2. Goto `about:addons` ## Firefox
3. Click on the top right button
4. Click `Install Add-on From File`
5. Select the Add-on
# How to build [Reddit thread](https://www.reddit.com/r/startpages/comments/g3qndt/psa_how_to_set_a_custom_new_tab_page_in_firefox/)
1. Build the bundle with `npm run buildProd` Create 2 files inside of the firefox install directory.
2. Provide the secrets required for signing the addon
3. Sign the Add-on by Mozilla with `npm run ext:sign`
# Usefull links `${installDir}/defaults/pref/autoconfig.js`
```js
pref("general.config.filename", "autoconfig.cfg");
pref("general.config.obscure_value", 0);
pref("general.config.sandbox_enabled", false);
```
- [Mozilla Developer hub](https://addons.mozilla.org/en-US/developers/) `${installDir}/autoconfig.cfg`
- [Mozilla Extension documentation](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions) ```
- [Web ext documentation](https://extensionworkshop.com/documentation/develop/web-ext-command-reference/) // first line is a comment
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.

4134
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,11 +6,7 @@
"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",
@ -20,8 +16,6 @@
"@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",
"case-sensitive-paths-webpack-plugin": "^2.3.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",
@ -32,8 +26,6 @@
"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"

View File

@ -1,6 +1,8 @@
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";
@ -12,9 +14,25 @@ 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>;
} }

View File

@ -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);
} }

View File

@ -1,43 +1,21 @@
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 State { interface Props {
bookmarks: Bookmarks; children: React.ReactNode;
} }
class Quick extends React.Component<{}, State> { class Quick extends React.Component<Props> {
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.state.bookmarks.folder.map((e, i) => { {this.props.children}
return <QuickCategory folder={e} key={i} />;
})}
</div> </div>
</div>; </div>;
} }

View File

@ -1,28 +1,25 @@
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 {
folder: BookmarkFolder; children: React.ReactNode;
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.folder.name} {this.props.name}
</div> </div>
<div className="children"> <div className="children">
{this.props.folder.bookmarks.map((e, i) => { {this.props.children}
return <QuickItem bookmark={e} key={i} />;
})}
</div> </div>
</div>; </div>;
} }

View File

@ -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 {
bookmark: Bookmark; name: string;
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.bookmark.url; window.location.href = this.props.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.bookmark.name} {this.props.name}
</div>; </div>;
} }

View File

@ -1,80 +1,30 @@
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";
interface State { class Search extends React.Component {
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();
} }
async sugest(input: string) { onKeyDown(event: React.KeyboardEvent) {
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;
} }
} }
@ -82,21 +32,9 @@ class Search extends React.Component<{}, State> {
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>;
} }

View File

@ -1,17 +0,0 @@
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;

View File

@ -1,43 +0,0 @@
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;
};

View File

@ -1,17 +0,0 @@
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]
};
};

View File

@ -1,25 +0,0 @@
{
"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}"
}
}
}

View File

@ -1,19 +1,12 @@
.search-component{ .search-component{
margin: 2em auto 2em auto; text-align: center;
width: 50%; margin-top: 2em;
position: relative; margin-bottom: 2em;
input[type=text]{ input[type=text]{
width: 100%; width: 40%;
border: none; border: none;
padding: 0.5em 1em; padding: 0.5em 1.5em;
box-sizing: border-box;
}
.suggestion-area{
position: absolute;
right: 0;
left: 0;
} }
} }

View File

@ -1,12 +0,0 @@
.suggestion-component{
padding: 0.2em 1em;
background-color: white;
color: black;
border: 1px solid #d4d4d4;
border-top: none;
cursor: pointer;
&:hover {
background-color: #d4d4d4;
}
}

View File

@ -1,16 +0,0 @@
interface Bookmarks {
folder: BookmarkFolder[];
orphans: Bookmark[];
}
interface BookmarkFolder {
name: string;
bookmarks: Bookmark[];
}
interface Bookmark {
name: string;
url: string;
}
export { Bookmarks, Bookmark, BookmarkFolder };

View File

@ -1,31 +0,0 @@
/*
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;

View File

@ -1,12 +0,0 @@
enum SuggestionType {
QUICK,
QUERY
}
interface Suggestion {
display: string;
type: SuggestionType;
url?: string;
}
export { SuggestionType, Suggestion };

View File

@ -6,7 +6,6 @@
"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"

View File

@ -3,24 +3,10 @@
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 CaseSensitivePathsPlugin = require("case-sensitive-paths-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";
let version = process.env.DRONE_TAG;
if(!version){
version = require('child_process')
.execSync("git describe --abbrev=0")
.toString();
}
version = version.replace("v", "").trim();
module.exports = { module.exports = {
context: path.join(__dirname, "src"), context: path.join(__dirname, "src"),
resolve: { resolve: {
@ -28,7 +14,7 @@ module.exports = {
}, },
entry: ["./index.tsx"], entry: ["./index.tsx"],
output: { output: {
path: path.join(__dirname, "dist/startpage"), path: path.join(__dirname, "dist"),
filename: "bundle.js" filename: "bundle.js"
}, },
devtool: DEVELOPMENT ? "source-map" : false, devtool: DEVELOPMENT ? "source-map" : false,
@ -41,9 +27,6 @@ module.exports = {
liveReload: true, liveReload: true,
watchContentBase: true watchContentBase: true
}, },
watchOptions: {
ignored: ['dist/**', 'node_modules/**']
},
module: { module: {
rules: [ rules: [
{ {
@ -75,19 +58,6 @@ 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),
}),
new CaseSensitivePathsPlugin()
] ]
}; };