Compare commits
5 Commits
86ae6acc92
...
master
Author | SHA1 | Date | |
---|---|---|---|
df28d3c411 | |||
bca3049c27 | |||
944e3d2f11 | |||
b00c97d100 | |||
73fcc340f2 |
26
Earthfile
Normal file
26
Earthfile
Normal 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
|
@ -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
6263
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@ -1,8 +1,7 @@
|
||||
{
|
||||
"name": "@hibas123/securenotes2",
|
||||
"version": "2.0.0-alpha.1",
|
||||
"version": "2.0.2",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "parcel build src/index.html",
|
||||
"dev": "parcel src/index.html"
|
||||
@ -13,25 +12,33 @@
|
||||
"last 2 Chrome versions"
|
||||
],
|
||||
"dependencies": {
|
||||
"@hibas123/secure-file-wrapper": "^2.5.1",
|
||||
"@hibas123/theme": "^1.0.5",
|
||||
"@hibas123/utils": "^2.2.17",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/lang-markdown": "^6.2.5",
|
||||
"@hibas123/secure-file-wrapper": "^2.5.3",
|
||||
"@hibas123/theme": "^2.0.7",
|
||||
"@hibas123/utils": "^2.2.18",
|
||||
"aes-js": "^3.1.2",
|
||||
"codemirror": "^5.58.3",
|
||||
"feather-icons": "^4.28.0",
|
||||
"idb": "^5.0.7",
|
||||
"js-sha256": "^0.9.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"easymde": "^2.18.0",
|
||||
"feather-icons": "^4.29.2",
|
||||
"idb": "^5.0.8",
|
||||
"js-sha256": "^0.11.0",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"parcel": "^1.12.4",
|
||||
"preact": "^10.5.7",
|
||||
"preact-feather": "^4.1.0",
|
||||
"secure-file-wrapper": "git+https://git.stamm.me/OpenServer/OSSecureFileWrapper.git",
|
||||
"uuid": "^8.3.1"
|
||||
"parcel": "^2.12.0",
|
||||
"preact": "^10.22.0",
|
||||
"preact-feather": "^4.2.1",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/codemirror": "0.0.99",
|
||||
"@types/lodash.clonedeep": "^4.5.6",
|
||||
"@types/uuid": "^8.3.0",
|
||||
"typescript": "^4.1.2"
|
||||
"@parcel/packager-raw-url": "2.12.0",
|
||||
"@parcel/transformer-sass": "^2.12.0",
|
||||
"@parcel/transformer-webmanifest": "^2.12.0",
|
||||
"@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
3701
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,13 @@
|
||||
import { h } from "preact";
|
||||
import { useEffect, useRef } from "preact/hooks";
|
||||
|
||||
import * as CM from "codemirror";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/base16-dark.css";
|
||||
import { EditorView, basicSetup, } from "codemirror";
|
||||
import { EditorState, Text } from "@codemirror/state";
|
||||
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 Theme from "../theme";
|
||||
@ -18,27 +22,41 @@ interface ICodeMirrorProps {
|
||||
export default function CodeMirror(props: ICodeMirrorProps) {
|
||||
const ref = useRef<HTMLTextAreaElement>();
|
||||
useEffect(() => {
|
||||
const instance = CM.fromTextArea(ref.current, {
|
||||
value: props.value,
|
||||
mode: "markdown",
|
||||
lineNumbers: true,
|
||||
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 instance = new EditorView({
|
||||
parent: ref.current,
|
||||
extensions: [
|
||||
basicSetup,
|
||||
markdown(),
|
||||
|
||||
],
|
||||
})
|
||||
// const instance = CM.(ref.current, {
|
||||
// value: props.value,
|
||||
// mode: "markdown",
|
||||
// lineNumbers: true,
|
||||
// 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]);
|
||||
return <textarea ref={ref}></textarea>;
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ export class Footer extends Component<
|
||||
<span>
|
||||
Welcome <b>{Notes.name}</b>
|
||||
</span>
|
||||
<span style="color: lightgrey;">v1.5</span>
|
||||
<span style="color: lightgrey;">v2.0.2</span>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
@ -15,16 +15,10 @@ export default class SettingsPage extends Page<
|
||||
return (
|
||||
<div>
|
||||
<header class="header">
|
||||
<a class="header-icon-button" onClick={() => history.back()}>
|
||||
<div class="header-icon-button" onClick={() => history.back()}>
|
||||
<ArrowLeft height={undefined} width={undefined} />
|
||||
</a>
|
||||
<h3
|
||||
style="display:inline"
|
||||
class="header-title"
|
||||
onClick={() => Navigation.setPage("/")}
|
||||
>
|
||||
Settings
|
||||
</h3>
|
||||
</div>
|
||||
<span onClick={() => Navigation.setPage("/")}>Settings</span>
|
||||
<span></span>
|
||||
</header>
|
||||
<div class="container">
|
||||
|
27
src/components/routes/vault/EasyMde.tsx
Normal file
27
src/components/routes/vault/EasyMde.tsx
Normal 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>
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -2,12 +2,16 @@ import { h, Component } from "preact";
|
||||
import { IVault, ViewNote } from "../../../notes";
|
||||
import { Trash2 as Trash, X, Save } from "preact-feather";
|
||||
|
||||
import Navigation from "../../../navigation";
|
||||
// import Navigation from "../../../navigation";
|
||||
import { YesNoModal } from "../../modals/YesNoModal";
|
||||
import Notifications, { MessageType } from "../../../notifications";
|
||||
import CodeMirror from "../../CodeMirror";
|
||||
// import CodeMirror from "../../CodeMirror";
|
||||
|
||||
|
||||
|
||||
import { useEffect, useMemo, useState } from "preact/hooks";
|
||||
import { usePromise } from "../../../hooks";
|
||||
import EasyMDEPreact from "./EasyMde";
|
||||
|
||||
interface IEntryProps {
|
||||
vault: IVault;
|
||||
@ -96,34 +100,32 @@ export default function Entry(props: IEntryProps) {
|
||||
return (
|
||||
<div>
|
||||
<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} />
|
||||
</a>
|
||||
</div>
|
||||
{changed && (
|
||||
<a
|
||||
<div
|
||||
class="header-icon-button"
|
||||
style="margin-left: 0.5em;"
|
||||
onClick={save}
|
||||
>
|
||||
<Save height={undefined} width={undefined} />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
<h3 style="display:inline" class="button header-title">
|
||||
{title}
|
||||
</h3>
|
||||
<a class="header-icon-button" onClick={del}>
|
||||
<span>{title}</span>
|
||||
<div class="header-icon-button" onClick={del}>
|
||||
<Trash height={undefined} width={undefined} />
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="container" style="padding: 0">
|
||||
<CodeMirror
|
||||
<EasyMDEPreact
|
||||
value={text}
|
||||
onChange={(value) => {
|
||||
setChanged(true);
|
||||
setText(value);
|
||||
}}
|
||||
// onSave={save}
|
||||
// onClose={close}
|
||||
// onSave={save}
|
||||
// onClose={close}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -300,22 +300,18 @@ export default class EntryList extends Component<
|
||||
<div>
|
||||
{this.state.context}
|
||||
<header class="header">
|
||||
<a class="header-icon-button" onClick={() => history.back()}>
|
||||
<div class="header-icon-button" onClick={() => history.back()}>
|
||||
<ArrowLeft height={undefined} width={undefined} />
|
||||
</a>
|
||||
<h3
|
||||
style="display:inline"
|
||||
class="header-title"
|
||||
onClick={() => Navigation.setPage("/")}
|
||||
>
|
||||
</div>
|
||||
<span onClick={() => Navigation.setPage("/")}>
|
||||
{this.vault ? this.vault.name : ""}
|
||||
</h3>
|
||||
</span>
|
||||
<span></span>
|
||||
{/* <a class="button header_icon_button"><MoreVertival height={undefined} width={undefined} /></a> */}
|
||||
</header>
|
||||
<AddButton onClick={() => open_entry(null)} />
|
||||
<div class="container">
|
||||
<div style="display:flex;">
|
||||
<div style="display:flex; margin-top: .5rem;">
|
||||
<input
|
||||
class="inp"
|
||||
type="text"
|
||||
|
@ -32,7 +32,7 @@ export default class VaultsPage extends Page<
|
||||
|
||||
updateVaults(s?: boolean) {
|
||||
if (s) return;
|
||||
return new Promise((yes) => {
|
||||
return new Promise<void>((yes) => {
|
||||
Notes.getVaults().then((vaults) => this.setState({ vaults }, yes));
|
||||
});
|
||||
}
|
||||
@ -245,6 +245,7 @@ export default class VaultsPage extends Page<
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log({ vaults: this.state.vaults })
|
||||
let elms = this.state.vaults.map((vault) => {
|
||||
return (
|
||||
<li
|
||||
@ -268,20 +269,20 @@ export default class VaultsPage extends Page<
|
||||
{this.state.context}
|
||||
<header class="header">
|
||||
<span></span>
|
||||
<h3
|
||||
<span
|
||||
style="display:inline"
|
||||
onClick={() => Navigation.setPage("/")}
|
||||
>
|
||||
{this.props.selectVault
|
||||
? "Select Vault for share"
|
||||
: "Your vaults:"}
|
||||
</h3>
|
||||
<a
|
||||
</span>
|
||||
<div
|
||||
class="header-icon-button"
|
||||
onClick={() => Navigation.setPage("/settings")}
|
||||
>
|
||||
<Settings height={undefined} width={undefined} />
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<AddButton onClick={() => this.addButtonClick()} />
|
||||
<div class="container">
|
||||
|
@ -1,70 +1,39 @@
|
||||
<!DOCTYPE html>
|
||||
<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 -->
|
||||
<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" />
|
||||
<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"> -->
|
||||
|
||||
<!-- sizes="180x180" -->
|
||||
<!-- href="/public/apple-touch-icon.png" -->
|
||||
<!-- Add to home screen for Safari on iOS -->
|
||||
<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
|
||||
rel="apple-touch-icon"
|
||||
sizes="256x256"
|
||||
href="/public/notepad256.png"
|
||||
/>
|
||||
<!-- <link
|
||||
<!-- sizes="180x180" -->
|
||||
<!-- href="/public/apple-touch-icon.png" -->
|
||||
|
||||
<link rel="apple-touch-icon" sizes="256x256" href="./public/notepad256.png" />
|
||||
<!-- <link
|
||||
rel="mask-icon"
|
||||
href="/public/safari-pinned-tab.svg"
|
||||
color="#1E88E5"
|
||||
/> -->
|
||||
<meta name="msapplication-TileColor" content="#1E88E5" />
|
||||
<meta name="theme-color" content="#1E88E5" />
|
||||
</head>
|
||||
<meta name="msapplication-TileColor" content="#1E88E5" />
|
||||
<meta name="theme-color" content="#1E88E5" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript> You have to enable JavaScript to use this site! </noscript>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
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("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>
|
||||
<body>
|
||||
<noscript> You have to enable JavaScript to use this site! </noscript>
|
||||
<div id="app"></div>
|
||||
<script>
|
||||
window.debug = {}
|
||||
</script>
|
||||
<script src="index.tsx" type="module"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -4,64 +4,14 @@ body {
|
||||
overscroll-behavior-y: contain;
|
||||
}
|
||||
|
||||
// html {
|
||||
// min-height: 100%;
|
||||
// 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;
|
||||
header {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.def-shadow {
|
||||
box-shadow: 0px 5px 8px 2px rgba(66, 66, 66, 0.53);
|
||||
}
|
||||
|
||||
|
||||
.ccontainer {
|
||||
margin: 0 1rem;
|
||||
max-width: 100%;
|
||||
@ -72,4 +22,4 @@ body {
|
||||
max-width: 50rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
145
src/index.tsx
145
src/index.tsx
@ -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 ||
|
||||
@ -42,7 +9,7 @@ window.requestIdleCallback =
|
||||
didTimeout: false,
|
||||
timeRemaining: function () {
|
||||
return Math.max(0, 50 - (Date.now() - start));
|
||||
}
|
||||
},
|
||||
});
|
||||
}, 1);
|
||||
};
|
||||
@ -57,34 +24,23 @@ console.log(window.requestIdleCallback);
|
||||
|
||||
window.debug = {};
|
||||
|
||||
|
||||
|
||||
|
||||
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 { h, render } from "preact";
|
||||
import App from "./components/App";
|
||||
import "@hibas123/theme/out/base.css";
|
||||
import "./index.scss";
|
||||
import Navigation from './navigation';
|
||||
import VaultsPage from './components/routes/vaults/Vaults';
|
||||
import { Page } from './page';
|
||||
import Navigation from "./navigation";
|
||||
import VaultsPage from "./components/routes/vaults/Vaults";
|
||||
import { Page } from "./page";
|
||||
|
||||
import Notes from "./notes"
|
||||
import DemoPage from './components/demo';
|
||||
import VaultPage from './components/routes/vault/Vault';
|
||||
import SharePage from './components/routes/share/Share';
|
||||
import Notifications from './notifications';
|
||||
import Error404Page from './components/routes/404';
|
||||
import SettingsPage from './components/routes/settings/Settings';
|
||||
import Notes from "./notes";
|
||||
import DemoPage from "./components/demo";
|
||||
import VaultPage from "./components/routes/vault/Vault";
|
||||
import SharePage from "./components/routes/share/Share";
|
||||
import Notifications from "./notifications";
|
||||
import Error404Page from "./components/routes/404";
|
||||
import SettingsPage from "./components/routes/settings/Settings";
|
||||
window.debug.notes = Notes;
|
||||
|
||||
|
||||
import Theme from "./theme";
|
||||
|
||||
console.log("Dark mode:", Theme.active);
|
||||
@ -92,46 +48,85 @@ console.log("Dark mode:", Theme.active);
|
||||
(async () => {
|
||||
// Initialize notes provider
|
||||
if (Notes.loginRequired()) {
|
||||
let url = new URL(location.href)
|
||||
let url = new URL(location.href);
|
||||
let code = url.searchParams.get("code");
|
||||
|
||||
if (code) {
|
||||
let err = await Notes.getToken(code)
|
||||
let err = await Notes.getToken(code);
|
||||
if (err) {
|
||||
Notifications.sendError("Login failed: " + err)
|
||||
return Notes.login()
|
||||
Notifications.sendError("Login failed: " + err);
|
||||
return Notes.login();
|
||||
} else {
|
||||
window.history.replaceState(null, document.title, "/" + window.location.hash);
|
||||
window.history.replaceState(
|
||||
null,
|
||||
document.title,
|
||||
"/" + window.location.hash
|
||||
);
|
||||
}
|
||||
} else {
|
||||
return Notes.login()
|
||||
return Notes.login();
|
||||
}
|
||||
}
|
||||
|
||||
await Notes.start();
|
||||
|
||||
if (window.navigator.storage && navigator.storage.persist) {
|
||||
navigator.storage.persisted()
|
||||
.then(has => has ? true : navigator.storage.persist())
|
||||
.then(is => {
|
||||
navigator.storage
|
||||
.persisted()
|
||||
.then((has) => (has ? true : navigator.storage.persist()))
|
||||
.then((is) => {
|
||||
console.log("Persistant Storage:", is);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
Navigation.default = VaultsPage as typeof Page;
|
||||
Navigation.addPage("/vault", VaultPage as typeof Page)
|
||||
Navigation.addPage("/demo", DemoPage as typeof Page)
|
||||
Navigation.addPage("/share", SharePage as typeof Page)
|
||||
Navigation.addPage("/settings", SettingsPage as typeof Page)
|
||||
Navigation.addPage("/vault", VaultPage as any as typeof Page);
|
||||
Navigation.addPage("/demo", DemoPage as typeof Page);
|
||||
Navigation.addPage("/share", SharePage as typeof Page);
|
||||
Navigation.addPage("/settings", SettingsPage as typeof Page);
|
||||
Navigation.addPage("/404", Error404Page);
|
||||
const trad = window.location.pathname;
|
||||
if (trad && trad !== "/" && trad !== "") {
|
||||
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);
|
||||
// window.location.href = "/#/" + trad;
|
||||
}
|
||||
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);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
24
src/notes.ts
24
src/notes.ts
@ -8,6 +8,7 @@ import * as config from "../config.json";
|
||||
import * as b64 from "./helper/base64";
|
||||
import IDB from "./helper/indexeddb";
|
||||
import Notifications, { MessageType } from "./notifications";
|
||||
import { cloneDeep } from "lodash";
|
||||
|
||||
export class HttpError extends Error {
|
||||
constructor(public status: number, public statusText: string) {
|
||||
@ -37,8 +38,6 @@ export interface ViewNote extends BaseNote {
|
||||
__value: string;
|
||||
}
|
||||
|
||||
import clonedeep = require("lodash.clonedeep");
|
||||
|
||||
const Encoder = new TextEncoder();
|
||||
const Decoder = new TextDecoder();
|
||||
|
||||
@ -129,11 +128,10 @@ class NotesProvider {
|
||||
}
|
||||
|
||||
login() {
|
||||
window.location.href = `${config.auth_server}/auth?client_id=${
|
||||
config.client_id
|
||||
}&scope=${config.permission}&redirect_uri=${encodeURIComponent(
|
||||
config.callback_url
|
||||
)}&response_type=code`;
|
||||
window.location.href = `${config.auth_server}/auth?client_id=${config.client_id
|
||||
}&scope=${config.permission}&redirect_uri=${encodeURIComponent(
|
||||
config.callback_url
|
||||
)}&response_type=code`;
|
||||
}
|
||||
|
||||
async getToken(code: string) {
|
||||
@ -193,8 +191,8 @@ class NotesProvider {
|
||||
console.log("Getting JWT");
|
||||
let req = await fetch(
|
||||
config.auth_server +
|
||||
"/api/oauth/jwt?refreshtoken=" +
|
||||
localStorage.getItem("refreshtoken")
|
||||
"/api/oauth/jwt?refreshtoken=" +
|
||||
localStorage.getItem("refreshtoken")
|
||||
);
|
||||
if (req.status !== 200) {
|
||||
Notifications.sendNotification("offline", MessageType.INFO);
|
||||
@ -356,7 +354,7 @@ class NotesProvider {
|
||||
let value = await this._secureFile.get(id);
|
||||
let note: DBNote = {
|
||||
_id: remote._id,
|
||||
folder: remote.folder,
|
||||
folder: remote.active.folder,
|
||||
preview: b64.decode(remote.active.preview || ""),
|
||||
time: remote.active.time,
|
||||
__value: new Uint8Array(value),
|
||||
@ -578,7 +576,7 @@ class NotesProvider {
|
||||
.filter((e) => e.folder === this.vault._id)
|
||||
.sort(this.sort)
|
||||
.map<BaseNote>((e) => {
|
||||
let new_note = clonedeep(<Note>e) as BaseNote;
|
||||
let new_note = cloneDeep(<Note>e) as BaseNote;
|
||||
delete (<any>new_note).__value;
|
||||
new_note.preview = this.decrypt(e.preview);
|
||||
return new_note;
|
||||
@ -610,7 +608,7 @@ class NotesProvider {
|
||||
const tx = Notes.database.transaction(Notes.noteDB, Notes.oplogDB);
|
||||
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);
|
||||
let [title, preview] = note.__value.split("\n");
|
||||
if (preview) preview = "\n" + preview;
|
||||
@ -637,7 +635,7 @@ class NotesProvider {
|
||||
async getNote(id: string): Promise<ViewNote> {
|
||||
let note = await Notes.noteDB.get(id);
|
||||
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);
|
||||
return new_note;
|
||||
}
|
||||
|
@ -1,23 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./public",
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h",
|
||||
"lib": [
|
||||
"es2015",
|
||||
"dom",
|
||||
"esnext"
|
||||
],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": [
|
||||
"./src/types.d.ts",
|
||||
"./src/**/*.tsx",
|
||||
"./src/**/*.ts"
|
||||
]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"outDir": "./public",
|
||||
"target": "ES2022",
|
||||
"module": "commonjs",
|
||||
"noImplicitAny": false,
|
||||
"removeComments": true,
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h",
|
||||
"lib": ["es2015", "dom", "esnext"],
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["./src/types.d.ts", "./src/**/*.tsx", "./src/**/*.ts"]
|
||||
}
|
||||
|
@ -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'],
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
Reference in New Issue
Block a user