Compare commits

5 Commits

Author SHA1 Message Date
df28d3c411 Add Earthfile and update version 2024-06-29 12:46:52 +02:00
bca3049c27 Update to be compatible with new SecureFile version 2024-06-22 20:37:15 +02:00
944e3d2f11 Remove config 2020-12-19 18:53:56 +01:00
b00c97d100 Fix style issues 2020-11-24 22:21:21 +01:00
73fcc340f2 Bump version 2020-11-24 22:07:06 +01:00
19 changed files with 3976 additions and 6855 deletions

26
Earthfile Normal file
View File

@ -0,0 +1,26 @@
VERSION 0.7
docker-multi:
BUILD --platform linux/amd64 --platform linux/arm64 +docker
build:
FROM node:lts
WORKDIR /build
COPY . .
RUN npm config set registry https://npm.hibas123.de
RUN npm install
RUN npm run build
SAVE ARTIFACT dist secure_notes_files
docker:
FROM busybox:1.36
WORKDIR /app
COPY --platform=linux/amd64 +build/secure_notes_files /app/
CMD ["busybox", "httpd", "-f", "-v", "-p", "3001", "-h", "/app"]
ARG EARTHLY_TARGET_TAG
ARG TAG=$EARTHLY_TARGET_TAG
SAVE IMAGE --push git.hibas.dev/openserver/secure_notes:$TAG

View File

@ -1,7 +0,0 @@
{
"secure_file_server": "https://sf.dev.hibas123.de",
"auth_server": "https://auth.stamm.me",
"client_id": "cf699b12-2014-4a61-bb7d-a7e38b197350",
"permission": "5c1ed399dc361731340a49d4",
"callback_url": "https://notes.dev.hibas123.de"
}

6263
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,7 @@
{ {
"name": "@hibas123/securenotes2", "name": "@hibas123/securenotes2",
"version": "2.0.0-alpha.1", "version": "2.0.2",
"description": "", "description": "",
"main": "index.js",
"scripts": { "scripts": {
"build": "parcel build src/index.html", "build": "parcel build src/index.html",
"dev": "parcel src/index.html" "dev": "parcel src/index.html"
@ -13,25 +12,33 @@
"last 2 Chrome versions" "last 2 Chrome versions"
], ],
"dependencies": { "dependencies": {
"@hibas123/secure-file-wrapper": "^2.5.1", "@codemirror/commands": "^6.6.0",
"@hibas123/theme": "^1.0.5", "@codemirror/lang-markdown": "^6.2.5",
"@hibas123/utils": "^2.2.17", "@hibas123/secure-file-wrapper": "^2.5.3",
"@hibas123/theme": "^2.0.7",
"@hibas123/utils": "^2.2.18",
"aes-js": "^3.1.2", "aes-js": "^3.1.2",
"codemirror": "^5.58.3", "codemirror": "^6.0.1",
"feather-icons": "^4.28.0", "easymde": "^2.18.0",
"idb": "^5.0.7", "feather-icons": "^4.29.2",
"js-sha256": "^0.9.0", "idb": "^5.0.8",
"js-sha256": "^0.11.0",
"lodash": "^4.17.21",
"lodash.clonedeep": "^4.5.0", "lodash.clonedeep": "^4.5.0",
"parcel": "^1.12.4", "parcel": "^2.12.0",
"preact": "^10.5.7", "preact": "^10.22.0",
"preact-feather": "^4.1.0", "preact-feather": "^4.2.1",
"secure-file-wrapper": "git+https://git.stamm.me/OpenServer/OSSecureFileWrapper.git", "uuid": "^10.0.0"
"uuid": "^8.3.1"
}, },
"devDependencies": { "devDependencies": {
"@types/codemirror": "0.0.99", "@parcel/packager-raw-url": "2.12.0",
"@types/lodash.clonedeep": "^4.5.6", "@parcel/transformer-sass": "^2.12.0",
"@types/uuid": "^8.3.0", "@parcel/transformer-webmanifest": "^2.12.0",
"typescript": "^4.1.2" "@types/codemirror": "5.60.15",
"@types/lodash.clonedeep": "^4.5.9",
"@types/uuid": "^10.0.0",
"process": "^0.11.10",
"sass": "^1.77.6",
"typescript": "^5.5.2"
} }
} }

3701
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,13 @@
import { h } from "preact"; import { h } from "preact";
import { useEffect, useRef } from "preact/hooks"; import { useEffect, useRef } from "preact/hooks";
import * as CM from "codemirror"; import { EditorView, basicSetup, } from "codemirror";
import "codemirror/lib/codemirror.css"; import { EditorState, Text } from "@codemirror/state";
import "codemirror/theme/base16-dark.css"; import { defaultKeymap } from "@codemirror/commands"
import { markdown } from "@codemirror/lang-markdown"
// import "codemirror/lib/codemirror.css";
// import "codemirror/theme/base16-dark.css";
import "./CodeMirror.scss"; import "./CodeMirror.scss";
import Theme from "../theme"; import Theme from "../theme";
@ -18,27 +22,41 @@ interface ICodeMirrorProps {
export default function CodeMirror(props: ICodeMirrorProps) { export default function CodeMirror(props: ICodeMirrorProps) {
const ref = useRef<HTMLTextAreaElement>(); const ref = useRef<HTMLTextAreaElement>();
useEffect(() => { useEffect(() => {
const instance = CM.fromTextArea(ref.current, { const instance = new EditorView({
value: props.value, parent: ref.current,
mode: "markdown", extensions: [
lineNumbers: true, basicSetup,
lineWrapping: true, markdown(),
theme: Theme.isDark.value ? "base16-dark" : "default",
viewportMargin: Infinity, ],
// extraKeys: { })
// "Ctrl-S": (cm) => { // const instance = CM.(ref.current, {
// const val = cm.getValue(); // value: props.value,
// props?.onSave(val); // mode: "markdown",
// }, // lineNumbers: true,
// Esc: props.onClose, // lineWrapping: true,
// }, // theme: Theme.isDark.value ? "base16-dark" : "default",
// viewportMargin: Infinity,
// // extraKeys: {
// // "Ctrl-S": (cm) => {
// // const val = cm.getValue();
// // props?.onSave(val);
// // },
// // Esc: props.onClose,
// // },
// });
const state = EditorState.create({
doc: Text.of([props.value ?? ""]),
}); });
if (props.value) instance.setValue(props.value); instance.setState(state);
instance.focus();
state.
instance.on("change", () => props?.onChange(instance.getValue())); instance.on("change", () => props?.onChange(instance.getValue()));
return () => {}; return () => { };
}, [ref]); }, [ref]);
return <textarea ref={ref}></textarea>; return <textarea ref={ref}></textarea>;
} }

View File

@ -72,7 +72,7 @@ export class Footer extends Component<
<span> <span>
Welcome <b>{Notes.name}</b> Welcome <b>{Notes.name}</b>
</span> </span>
<span style="color: lightgrey;">v1.5</span> <span style="color: lightgrey;">v2.0.2</span>
</footer> </footer>
); );
} }

View File

@ -15,16 +15,10 @@ export default class SettingsPage extends Page<
return ( return (
<div> <div>
<header class="header"> <header class="header">
<a class="header-icon-button" onClick={() => history.back()}> <div class="header-icon-button" onClick={() => history.back()}>
<ArrowLeft height={undefined} width={undefined} /> <ArrowLeft height={undefined} width={undefined} />
</a> </div>
<h3 <span onClick={() => Navigation.setPage("/")}>Settings</span>
style="display:inline"
class="header-title"
onClick={() => Navigation.setPage("/")}
>
Settings
</h3>
<span></span> <span></span>
</header> </header>
<div class="container"> <div class="container">

View File

@ -0,0 +1,27 @@
import { h } from "preact";
import * as EasyMDE from "easymde";
import "easymde/dist/easymde.min.css"
// import "./easymde.scss"
import { useEffect, useRef } from "preact/hooks";
export default function EasyMDEPreact(props: { value: string, onChange: (value: string) => void }) {
const ref = useRef<HTMLTextAreaElement>();
useEffect(() => {
const mde = new EasyMDE({
element: ref.current,
initialValue: props.value
})
mde.codemirror.on("change", () => {
props.onChange(mde.value());
});
return () => {
mde.cleanup();
}
}, [ref])
return <div style="background: white;">
<textarea ref={ref}></textarea>
</div>
}

View File

@ -1,209 +0,0 @@
import { h, Component } from "preact";
import { IVault, ViewNote } from "../../../notes";
import { Trash2 as Trash, X, Save } from "preact-feather";
import Navigation from "../../../navigation";
import { YesNoModal } from "../../modals/YesNoModal";
import Notifications, { MessageType } from "../../../notifications";
const minRows = 3;
export default class EntryComponent extends Component<
{ vault: Promise<IVault>; id: string | undefined; note: string | undefined },
{ title: string; changed: boolean }
> {
private text: string = "";
private vault: IVault;
// private lineHeight: number = 24;
private note: ViewNote;
private rows: number = minRows;
private skip_save: boolean = false;
private inputElm: HTMLInputElement;
constructor(props) {
super(props);
this.state = { changed: false, title: "" };
this.textAreaChange = this.textAreaChange.bind(this);
this.textAreaKeyPress = this.textAreaKeyPress.bind(this);
}
private toVault() {
history.back();
}
componentDidMount() {
this.inputElm.focus();
}
async componentWillMount() {
this.text = "";
this.vault = undefined;
this.note = undefined;
this.rows = minRows;
this.skip_save = false;
this.setState({ changed: false, title: "" });
try {
this.skip_save = false;
this.vault = await this.props.vault;
let note: ViewNote;
let changed = false;
if (this.props.id) note = await this.vault.getNote(this.props.id);
else {
note = this.vault.newNote();
if (this.props.note) {
note.__value = this.props.note;
changed = true;
}
}
if (!note) {
Notifications.sendNotification(
"Note not found!",
MessageType.ERROR
);
} else {
this.note = note;
this.text = note.__value;
let rows = this.getRows(this.text);
if (rows !== this.rows) {
this.rows = rows;
}
let [title] = this.text.split("\n", 1);
this.setState({ title, changed });
}
} catch (err) {
Notifications.sendError(err);
}
}
private async save() {
try {
if (this.state.changed) {
this.note.__value = this.text;
await this.vault.saveNote(this.note);
this.setState({ changed: false });
}
} catch (err) {
Notifications.sendError(err);
}
}
componentWillUnmount() {
if (!this.skip_save) this.save();
}
strToNr(value: string) {
let match = value.match(/\d/g);
return Number(match.join(""));
}
getRows(value: string) {
const lines = (value.match(/\r?\n/g) || "").length + 1;
return Math.max(lines + 1, minRows);
}
textAreaChange(evt: KeyboardEvent) {
if (evt.keyCode === 17 || evt.keyCode === 27) return; //No character, so not relevant for this function
if (!this.state.changed && this.textAreaKeyPress(evt))
this.setState({ changed: true });
let target = evt.target as HTMLTextAreaElement;
let value = target.value;
this.text = value;
let [title] = value.split("\n", 1);
if (title !== this.state.title) this.setState({ title });
let rows = this.getRows(value);
if (rows !== this.rows) {
target.rows = rows;
this.rows = rows;
}
}
async exitHandler() {
if (this.state.changed) {
let modal = new YesNoModal("Really want to quit?");
let res = await modal.getResult();
if (res === true) {
this.skip_save = true;
this.toVault();
}
} else this.toVault();
}
textAreaKeyPress(evt: KeyboardEvent) {
console.log(evt);
if ((evt.keyCode === 83 || evt.keyCode === 13) && evt.ctrlKey) {
evt.preventDefault();
this.save();
return false;
} else if (evt.keyCode === 27) {
evt.preventDefault();
this.exitHandler();
return false;
}
return true;
}
render() {
const save_handler = async () => {
await this.save();
Navigation.setPage(
"/vault",
{ id: this.vault.id },
{ entry: "false" },
true
);
this.toVault();
};
const delete_handler = async () => {
await this.vault.deleteNote(this.props.id);
this.toVault();
};
console.log("Rerender");
return (
<div>
<header class="header">
<a class="header-icon-button" onClick={() => this.exitHandler()}>
<X height={undefined} width={undefined} />
</a>
{this.state.changed ? (
<a
class="header-icon-button"
style="margin-left: 0.5em;"
onClick={() => save_handler()}
>
<Save height={undefined} width={undefined} />
</a>
) : undefined}
<h3 style="display:inline" class="button header-title">
{this.state.title}
</h3>
<a class="header-icon-button" onClick={() => delete_handler()}>
<Trash height={undefined} width={undefined} />
</a>
</header>
<div class="container">
<textarea
value={this.text}
rows={this.rows}
class="inp"
style="width:100%;"
onInput={this.textAreaChange}
onKeyDown={this.textAreaKeyPress}
onChange={this.textAreaChange}
ref={(elm) => (this.inputElm = elm)}
/>
</div>
</div>
);
}
}

View File

@ -2,12 +2,16 @@ import { h, Component } from "preact";
import { IVault, ViewNote } from "../../../notes"; import { IVault, ViewNote } from "../../../notes";
import { Trash2 as Trash, X, Save } from "preact-feather"; import { Trash2 as Trash, X, Save } from "preact-feather";
import Navigation from "../../../navigation"; // import Navigation from "../../../navigation";
import { YesNoModal } from "../../modals/YesNoModal"; import { YesNoModal } from "../../modals/YesNoModal";
import Notifications, { MessageType } from "../../../notifications"; import Notifications, { MessageType } from "../../../notifications";
import CodeMirror from "../../CodeMirror"; // import CodeMirror from "../../CodeMirror";
import { useEffect, useMemo, useState } from "preact/hooks"; import { useEffect, useMemo, useState } from "preact/hooks";
import { usePromise } from "../../../hooks"; import { usePromise } from "../../../hooks";
import EasyMDEPreact from "./EasyMde";
interface IEntryProps { interface IEntryProps {
vault: IVault; vault: IVault;
@ -96,34 +100,32 @@ export default function Entry(props: IEntryProps) {
return ( return (
<div> <div>
<header class="header" style="margin-bottom: 0;"> <header class="header" style="margin-bottom: 0;">
<a class="header-icon-button" onClick={close}> <div class="header-icon-button" onClick={close}>
<X height={undefined} width={undefined} /> <X height={undefined} width={undefined} />
</a> </div>
{changed && ( {changed && (
<a <div
class="header-icon-button" class="header-icon-button"
style="margin-left: 0.5em;" style="margin-left: 0.5em;"
onClick={save} onClick={save}
> >
<Save height={undefined} width={undefined} /> <Save height={undefined} width={undefined} />
</a> </div>
)} )}
<h3 style="display:inline" class="button header-title"> <span>{title}</span>
{title} <div class="header-icon-button" onClick={del}>
</h3>
<a class="header-icon-button" onClick={del}>
<Trash height={undefined} width={undefined} /> <Trash height={undefined} width={undefined} />
</a> </div>
</header> </header>
<div class="container" style="padding: 0"> <div class="container" style="padding: 0">
<CodeMirror <EasyMDEPreact
value={text} value={text}
onChange={(value) => { onChange={(value) => {
setChanged(true); setChanged(true);
setText(value); setText(value);
}} }}
// onSave={save} // onSave={save}
// onClose={close} // onClose={close}
/> />
</div> </div>
</div> </div>

View File

@ -300,22 +300,18 @@ export default class EntryList extends Component<
<div> <div>
{this.state.context} {this.state.context}
<header class="header"> <header class="header">
<a class="header-icon-button" onClick={() => history.back()}> <div class="header-icon-button" onClick={() => history.back()}>
<ArrowLeft height={undefined} width={undefined} /> <ArrowLeft height={undefined} width={undefined} />
</a> </div>
<h3 <span onClick={() => Navigation.setPage("/")}>
style="display:inline"
class="header-title"
onClick={() => Navigation.setPage("/")}
>
{this.vault ? this.vault.name : ""} {this.vault ? this.vault.name : ""}
</h3> </span>
<span></span> <span></span>
{/* <a class="button header_icon_button"><MoreVertival height={undefined} width={undefined} /></a> */} {/* <a class="button header_icon_button"><MoreVertival height={undefined} width={undefined} /></a> */}
</header> </header>
<AddButton onClick={() => open_entry(null)} /> <AddButton onClick={() => open_entry(null)} />
<div class="container"> <div class="container">
<div style="display:flex;"> <div style="display:flex; margin-top: .5rem;">
<input <input
class="inp" class="inp"
type="text" type="text"

View File

@ -32,7 +32,7 @@ export default class VaultsPage extends Page<
updateVaults(s?: boolean) { updateVaults(s?: boolean) {
if (s) return; if (s) return;
return new Promise((yes) => { return new Promise<void>((yes) => {
Notes.getVaults().then((vaults) => this.setState({ vaults }, yes)); Notes.getVaults().then((vaults) => this.setState({ vaults }, yes));
}); });
} }
@ -245,6 +245,7 @@ export default class VaultsPage extends Page<
} }
render() { render() {
console.log({ vaults: this.state.vaults })
let elms = this.state.vaults.map((vault) => { let elms = this.state.vaults.map((vault) => {
return ( return (
<li <li
@ -268,20 +269,20 @@ export default class VaultsPage extends Page<
{this.state.context} {this.state.context}
<header class="header"> <header class="header">
<span></span> <span></span>
<h3 <span
style="display:inline" style="display:inline"
onClick={() => Navigation.setPage("/")} onClick={() => Navigation.setPage("/")}
> >
{this.props.selectVault {this.props.selectVault
? "Select Vault for share" ? "Select Vault for share"
: "Your vaults:"} : "Your vaults:"}
</h3> </span>
<a <div
class="header-icon-button" class="header-icon-button"
onClick={() => Navigation.setPage("/settings")} onClick={() => Navigation.setPage("/settings")}
> >
<Settings height={undefined} width={undefined} /> <Settings height={undefined} width={undefined} />
</a> </div>
</header> </header>
<AddButton onClick={() => this.addButtonClick()} /> <AddButton onClick={() => this.addButtonClick()} />
<div class="container"> <div class="container">

View File

@ -1,70 +1,39 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<title>SecureNotes</title>
<meta charset="utf8" />
<meta name="Description" content="Notes app" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="manifest" href="./manifest.webmanifest" />
<!-- <link rel="shortcut icon" href="/public/icon-72x72.png"> -->
<!-- Add to home screen for Safari on iOS --> <head>
<meta name="apple-mobile-web-app-capable" content="yes" /> <title>SecureNotes</title>
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta charset="utf8" />
<meta name="apple-mobile-web-app-title" content="Secure Notes" /> <meta name="Description" content="Notes app" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<link rel="manifest" href="./manifest.webmanifest" />
<!-- <link rel="shortcut icon" href="/public/icon-72x72.png"> -->
<!-- sizes="180x180" --> <!-- Add to home screen for Safari on iOS -->
<!-- href="/public/apple-touch-icon.png" --> <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content="Secure Notes" />
<link <!-- sizes="180x180" -->
rel="apple-touch-icon" <!-- href="/public/apple-touch-icon.png" -->
sizes="256x256"
href="/public/notepad256.png" <link rel="apple-touch-icon" sizes="256x256" href="./public/notepad256.png" />
/> <!-- <link
<!-- <link
rel="mask-icon" rel="mask-icon"
href="/public/safari-pinned-tab.svg" href="/public/safari-pinned-tab.svg"
color="#1E88E5" color="#1E88E5"
/> --> /> -->
<meta name="msapplication-TileColor" content="#1E88E5" /> <meta name="msapplication-TileColor" content="#1E88E5" />
<meta name="theme-color" content="#1E88E5" /> <meta name="theme-color" content="#1E88E5" />
</head> </head>
<body> <body>
<noscript> You have to enable JavaScript to use this site! </noscript> <noscript> You have to enable JavaScript to use this site! </noscript>
<div id="app"></div> <div id="app"></div>
<script> <script>
if (navigator.serviceWorker.controller) { window.debug = {}
if (localStorage.getItem("debug")) { </script>
console.warn( <script src="index.tsx" type="module"></script>
"Debuggung and service worker found, make shure to clear cache!" </body>
);
} </html>
console.log("active service worker found, no need to register");
} else {
if (localStorage.getItem("debug")) {
console.warn("Disabling Service Worker in debug mode!");
} else {
// Register the ServiceWorker
navigator.serviceWorker
.register("serviceworker.js", {
scope: "/",
})
.then(function (reg) {
console.log(
"Service worker has been registered for scope:" +
reg.scope
);
navigator.serviceWorker.controller.addEventListener(
"cleared_cache",
(evt) => {
console.log(evt);
}
);
});
}
}
</script>
<script src="index.tsx"></script>
</body>
</html>

View File

@ -4,64 +4,14 @@ body {
overscroll-behavior-y: contain; overscroll-behavior-y: contain;
} }
// html { header {
// min-height: 100%; justify-content: space-between;
// display: flex;
// }
// $header-margin: .5em;
// $header-icon-size: calc(1.5rem * 1);
// $header-icon-size: 100%;
// header {
// display: flex;
// justify-content: space-between;
// margin-bottom: $header-margin;
// align-content: center;
// padding: 0.75em;
// border-bottom: solid var(--border-color) 1px;
// background-color: var(--primary);
// >* {
// margin: 0;
// color: white;
// background: var(--primary);
// }
// >.header-title {
// overflow: hidden;
// text-overflow: ellipsis;
// text-overflow: ellipsis;
// white-space: nowrap;
// overflow: hidden;
// flex-grow: 1;
// margin: 0 1em;
// }
// .header-icon-button {
// display: block;
// margin-top: auto;
// margin-bottom: auto;
// min-width: $header-icon-size;
// >svg {
// height: $header-icon-size;
// width: $header-icon-size;
// }
// }
// }
body {
overscroll-behavior-y: contain;
} }
.def-shadow { .def-shadow {
box-shadow: 0px 5px 8px 2px rgba(66, 66, 66, 0.53); box-shadow: 0px 5px 8px 2px rgba(66, 66, 66, 0.53);
} }
.ccontainer { .ccontainer {
margin: 0 1rem; margin: 0 1rem;
max-width: 100%; max-width: 100%;
@ -72,4 +22,4 @@ body {
max-width: 50rem; max-width: 50rem;
margin: 0 auto; margin: 0 auto;
} }
} }

View File

@ -1,36 +1,3 @@
declare global {
interface Window {
requestIdleCallback: (callback: (deadline: { didTimeout: boolean, timeRemaining: () => number }) => void, options?: { timeout: number }) => number | NodeJS.Timeout;
cancelIdleCallback: (id: number | NodeJS.Timeout) => void;
debug: any;
}
// namespace JSX {
// interface IntrinsicElements {
// "wired-button": HTMLAttributes;
// "wired-card": HTMLAttributes;
// "wired-checkbox": HTMLAttributes;
// "wired-combo": HTMLAttributes;
// "wired-fab": HTMLAttributes;
// "wired-icon-button": HTMLAttributes;
// "wired-input": HTMLAttributes;
// "wired-item": HTMLAttributes;
// "wired-lib": HTMLAttributes;
// "wired-listbox": HTMLAttributes;
// "wired-progress": HTMLAttributes;
// "wired-radio-group": HTMLAttributes;
// "wired-radio": HTMLAttributes;
// "wired-slider": HTMLAttributes;
// "wired-spinner": HTMLAttributes;
// "wired-tabs": HTMLAttributes;
// "wired-textarea": HTMLAttributes;
// "wired-toggle": HTMLAttributes;
// "wired-tooltip": HTMLAttributes;
// }
// }
}
declare const window: Window;
window.requestIdleCallback = window.requestIdleCallback =
window.requestIdleCallback || window.requestIdleCallback ||
@ -42,7 +9,7 @@ window.requestIdleCallback =
didTimeout: false, didTimeout: false,
timeRemaining: function () { timeRemaining: function () {
return Math.max(0, 50 - (Date.now() - start)); return Math.max(0, 50 - (Date.now() - start));
} },
}); });
}, 1); }, 1);
}; };
@ -57,34 +24,23 @@ console.log(window.requestIdleCallback);
window.debug = {}; window.debug = {};
import { h, render } from "preact";
import App from "./components/App";
import "@hibas123/theme/out/base.css";
import { h, render } from 'preact';
import App from './components/App';
// import "uikit";
// import "uikit/dist/css/uikit.css"
import "@hibas123/theme/out/base.css"
// import "@hibas123/theme/out/light.css"
// import "@hibas123/theme/out/dark.css"
import "./index.scss"; import "./index.scss";
import Navigation from './navigation'; import Navigation from "./navigation";
import VaultsPage from './components/routes/vaults/Vaults'; import VaultsPage from "./components/routes/vaults/Vaults";
import { Page } from './page'; import { Page } from "./page";
import Notes from "./notes" import Notes from "./notes";
import DemoPage from './components/demo'; import DemoPage from "./components/demo";
import VaultPage from './components/routes/vault/Vault'; import VaultPage from "./components/routes/vault/Vault";
import SharePage from './components/routes/share/Share'; import SharePage from "./components/routes/share/Share";
import Notifications from './notifications'; import Notifications from "./notifications";
import Error404Page from './components/routes/404'; import Error404Page from "./components/routes/404";
import SettingsPage from './components/routes/settings/Settings'; import SettingsPage from "./components/routes/settings/Settings";
window.debug.notes = Notes; window.debug.notes = Notes;
import Theme from "./theme"; import Theme from "./theme";
console.log("Dark mode:", Theme.active); console.log("Dark mode:", Theme.active);
@ -92,46 +48,85 @@ console.log("Dark mode:", Theme.active);
(async () => { (async () => {
// Initialize notes provider // Initialize notes provider
if (Notes.loginRequired()) { if (Notes.loginRequired()) {
let url = new URL(location.href) let url = new URL(location.href);
let code = url.searchParams.get("code"); let code = url.searchParams.get("code");
if (code) { if (code) {
let err = await Notes.getToken(code) let err = await Notes.getToken(code);
if (err) { if (err) {
Notifications.sendError("Login failed: " + err) Notifications.sendError("Login failed: " + err);
return Notes.login() return Notes.login();
} else { } else {
window.history.replaceState(null, document.title, "/" + window.location.hash); window.history.replaceState(
null,
document.title,
"/" + window.location.hash
);
} }
} else { } else {
return Notes.login() return Notes.login();
} }
} }
await Notes.start(); await Notes.start();
if (window.navigator.storage && navigator.storage.persist) { if (window.navigator.storage && navigator.storage.persist) {
navigator.storage.persisted() navigator.storage
.then(has => has ? true : navigator.storage.persist()) .persisted()
.then(is => { .then((has) => (has ? true : navigator.storage.persist()))
.then((is) => {
console.log("Persistant Storage:", is); console.log("Persistant Storage:", is);
}) });
} }
Navigation.default = VaultsPage as typeof Page; Navigation.default = VaultsPage as typeof Page;
Navigation.addPage("/vault", VaultPage as typeof Page) Navigation.addPage("/vault", VaultPage as any as typeof Page);
Navigation.addPage("/demo", DemoPage as typeof Page) Navigation.addPage("/demo", DemoPage as typeof Page);
Navigation.addPage("/share", SharePage as typeof Page) Navigation.addPage("/share", SharePage as typeof Page);
Navigation.addPage("/settings", SettingsPage as typeof Page) Navigation.addPage("/settings", SettingsPage as typeof Page);
Navigation.addPage("/404", Error404Page); Navigation.addPage("/404", Error404Page);
const trad = window.location.pathname; const trad = window.location.pathname;
if (trad && trad !== "/" && trad !== "") { if (trad && trad !== "/" && trad !== "") {
let p: any = {}; let p: any = {};
new URL(window.location.href).searchParams.forEach((val, key) => p[key] = val); new URL(window.location.href).searchParams.forEach(
(val, key) => (p[key] = val)
);
Navigation.setPage(trad, p, undefined, true); Navigation.setPage(trad, p, undefined, true);
// window.location.href = "/#/" + trad; // window.location.href = "/#/" + trad;
} }
Navigation.start(); Navigation.start();
render(<App />, document.body, document.getElementById('app')); render(<App />, document.body, document.getElementById("app"));
})() })();
if (navigator.serviceWorker.controller) {
if (localStorage.getItem("debug")) {
console.warn(
"Debuggung and service worker found, make shure to clear cache!"
);
}
console.log("active service worker found, no need to register");
} else {
if (localStorage.getItem("debug")) {
console.warn("Disabling Service Worker in debug mode!");
} else {
// Register the ServiceWorker
navigator.serviceWorker
.register(new URL("serviceworker.js", import.meta.url), {
scope: "/",
})
.then(function (reg) {
console.log(
"Service worker has been registered for scope:" +
reg.scope
);
navigator.serviceWorker.controller.addEventListener(
"cleared_cache",
(evt) => {
console.log(evt);
}
);
});
}
}

View File

@ -8,6 +8,7 @@ import * as config from "../config.json";
import * as b64 from "./helper/base64"; import * as b64 from "./helper/base64";
import IDB from "./helper/indexeddb"; import IDB from "./helper/indexeddb";
import Notifications, { MessageType } from "./notifications"; import Notifications, { MessageType } from "./notifications";
import { cloneDeep } from "lodash";
export class HttpError extends Error { export class HttpError extends Error {
constructor(public status: number, public statusText: string) { constructor(public status: number, public statusText: string) {
@ -37,8 +38,6 @@ export interface ViewNote extends BaseNote {
__value: string; __value: string;
} }
import clonedeep = require("lodash.clonedeep");
const Encoder = new TextEncoder(); const Encoder = new TextEncoder();
const Decoder = new TextDecoder(); const Decoder = new TextDecoder();
@ -129,11 +128,10 @@ class NotesProvider {
} }
login() { login() {
window.location.href = `${config.auth_server}/auth?client_id=${ window.location.href = `${config.auth_server}/auth?client_id=${config.client_id
config.client_id }&scope=${config.permission}&redirect_uri=${encodeURIComponent(
}&scope=${config.permission}&redirect_uri=${encodeURIComponent( config.callback_url
config.callback_url )}&response_type=code`;
)}&response_type=code`;
} }
async getToken(code: string) { async getToken(code: string) {
@ -193,8 +191,8 @@ class NotesProvider {
console.log("Getting JWT"); console.log("Getting JWT");
let req = await fetch( let req = await fetch(
config.auth_server + config.auth_server +
"/api/oauth/jwt?refreshtoken=" + "/api/oauth/jwt?refreshtoken=" +
localStorage.getItem("refreshtoken") localStorage.getItem("refreshtoken")
); );
if (req.status !== 200) { if (req.status !== 200) {
Notifications.sendNotification("offline", MessageType.INFO); Notifications.sendNotification("offline", MessageType.INFO);
@ -356,7 +354,7 @@ class NotesProvider {
let value = await this._secureFile.get(id); let value = await this._secureFile.get(id);
let note: DBNote = { let note: DBNote = {
_id: remote._id, _id: remote._id,
folder: remote.folder, folder: remote.active.folder,
preview: b64.decode(remote.active.preview || ""), preview: b64.decode(remote.active.preview || ""),
time: remote.active.time, time: remote.active.time,
__value: new Uint8Array(value), __value: new Uint8Array(value),
@ -578,7 +576,7 @@ class NotesProvider {
.filter((e) => e.folder === this.vault._id) .filter((e) => e.folder === this.vault._id)
.sort(this.sort) .sort(this.sort)
.map<BaseNote>((e) => { .map<BaseNote>((e) => {
let new_note = clonedeep(<Note>e) as BaseNote; let new_note = cloneDeep(<Note>e) as BaseNote;
delete (<any>new_note).__value; delete (<any>new_note).__value;
new_note.preview = this.decrypt(e.preview); new_note.preview = this.decrypt(e.preview);
return new_note; return new_note;
@ -610,7 +608,7 @@ class NotesProvider {
const tx = Notes.database.transaction(Notes.noteDB, Notes.oplogDB); const tx = Notes.database.transaction(Notes.noteDB, Notes.oplogDB);
let old_note = await Notes.noteDB.get(note._id, tx); let old_note = await Notes.noteDB.get(note._id, tx);
let new_note = clonedeep(<Note>note) as DBNote; let new_note = cloneDeep(<Note>note) as DBNote;
new_note.__value = this.encrypt(note.__value); new_note.__value = this.encrypt(note.__value);
let [title, preview] = note.__value.split("\n"); let [title, preview] = note.__value.split("\n");
if (preview) preview = "\n" + preview; if (preview) preview = "\n" + preview;
@ -637,7 +635,7 @@ class NotesProvider {
async getNote(id: string): Promise<ViewNote> { async getNote(id: string): Promise<ViewNote> {
let note = await Notes.noteDB.get(id); let note = await Notes.noteDB.get(id);
if (!note) return undefined; if (!note) return undefined;
let new_note = clonedeep(<Note>note) as ViewNote; let new_note = cloneDeep(<Note>note) as ViewNote;
new_note.__value = this.decrypt(note.__value); new_note.__value = this.decrypt(note.__value);
return new_note; return new_note;
} }

View File

@ -1,23 +1,15 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "./public", "outDir": "./public",
"target": "es6", "target": "ES2022",
"module": "commonjs", "module": "commonjs",
"noImplicitAny": false, "noImplicitAny": false,
"removeComments": true, "removeComments": true,
"sourceMap": true, "sourceMap": true,
"jsx": "react", "jsx": "react",
"jsxFactory": "h", "jsxFactory": "h",
"lib": [ "lib": ["es2015", "dom", "esnext"],
"es2015", "resolveJsonModule": true
"dom", },
"esnext" "include": ["./src/types.d.ts", "./src/**/*.tsx", "./src/**/*.ts"]
], }
"resolveJsonModule": true
},
"include": [
"./src/types.d.ts",
"./src/**/*.tsx",
"./src/**/*.ts"
]
}

View File

@ -1,76 +0,0 @@
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const Visualizer = require('webpack-visualizer-plugin');
const path = require("path");
const fs = require("fs");
const config = require("./config.json");
const url = require("url");
module.exports = {
devServer: {
host: "0.0.0.0", // Defaults to `localhost`
open: false, // Open the page in browser,
contentBase: path.join(__dirname, 'dist'),
disableHostCheck: true,
headers: {
"Access-Control-Allow-Origin": "*"
},
watchOptions: {
aggregateTimeout: 300,
poll: 500
},
public: url.parse(config.callback_url).hostname
},
plugins: [
new CopyWebpackPlugin([{
from: "public"
}]),
new HtmlWebpackPlugin({
title: 'SecureNotes',
template: "./src/index.html"
}),
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: "[id].css"
}),
new BundleAnalyzerPlugin({
analyzerMode: "static",
openAnalyzer: false
}),
new Visualizer(),
],
entry: {
main: "./src/index.tsx"
},
// Enable sourcemaps for debugging webpack's output.
devtool: "eval",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js", ".mjs"]
},
module: {
rules: [{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/
}, {
test: /\.(png|jpg)$/,
loader: "file-loader",
}, {
test: /\.(scss|css)$/,
use: [
"style-loader", // creates style nodes from JS strings
MiniCssExtractPlugin.loader,
"css-loader", // translates CSS into CommonJS
"sass-loader" // compiles Sass to CSS, using Node Sass by default
]
},
{
test: /\.svg$/,
use: ['preact-svg-loader'],
}
]
}
};