First alpha
This commit is contained in:
		
							
								
								
									
										7
									
								
								src/components/AddButton.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								src/components/AddButton.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
import { h } from "preact";
 | 
			
		||||
import "./add_button.scss";
 | 
			
		||||
import Plus from "feather-icons/dist/icons/plus.svg";
 | 
			
		||||
 | 
			
		||||
export default function AddButton({ onClick }: { onClick: () => void }) {
 | 
			
		||||
   return <button class="add_button_button circular primary" onClick={() => onClick()}><Plus width={undefined} height={undefined} /></button>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										6
									
								
								src/components/App.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										6
									
								
								src/components/App.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
import { Router } from "./Routing";
 | 
			
		||||
import { h } from "preact";
 | 
			
		||||
 | 
			
		||||
export default function App() {
 | 
			
		||||
   return <Router></Router>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								src/components/ContextMenu.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										0
									
								
								src/components/ContextMenu.tsx
									
									
									
									
									
										Executable file
									
								
							
							
								
								
									
										56
									
								
								src/components/Routing.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										56
									
								
								src/components/Routing.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
import { h, Component } from 'preact';
 | 
			
		||||
import Navigation from '../navigation';
 | 
			
		||||
import "./routing.scss"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class Router extends Component<{}, { next?: JSX.Element, current: JSX.Element }> {
 | 
			
		||||
   mounted: HTMLDivElement = undefined;
 | 
			
		||||
   constructor(props) {
 | 
			
		||||
      super(props);
 | 
			
		||||
      this.onChange = this.onChange.bind(this);
 | 
			
		||||
      this.state = { current: Navigation.page.page, next: undefined }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   componentWillMount() {
 | 
			
		||||
      Navigation.pageObservable.subscribe(this.onChange, true)
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   componentWillUnmount() {
 | 
			
		||||
      Navigation.pageObservable.unsubscribe(this.onChange)
 | 
			
		||||
   }
 | 
			
		||||
   to = -1;
 | 
			
		||||
   onChange([page]: JSX.Element[]) {
 | 
			
		||||
      this.setState({ next: page, current: this.state.next || this.state.current });
 | 
			
		||||
      if (this.to >= 0) {
 | 
			
		||||
         clearTimeout(this.to)
 | 
			
		||||
         this.to = -1;
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      let overlay;
 | 
			
		||||
      if (this.state.next) {
 | 
			
		||||
         overlay = <div class="transition_container transition_slidein" key={this.state.next.key} ref={(elm: HTMLDivElement) => {
 | 
			
		||||
            let lst = () => {
 | 
			
		||||
               if (this.state.next)
 | 
			
		||||
                  this.setState({ current: this.state.next, next: undefined }, () => {
 | 
			
		||||
                     if (this.mounted)
 | 
			
		||||
                        this.mounted.scrollTo({ top: 0 })
 | 
			
		||||
                  });
 | 
			
		||||
               if (elm)
 | 
			
		||||
                  elm.removeEventListener("animationend", lst)
 | 
			
		||||
            }
 | 
			
		||||
            if (elm)
 | 
			
		||||
               elm.addEventListener("animationend", lst)
 | 
			
		||||
         }}>
 | 
			
		||||
            {this.state.next}
 | 
			
		||||
         </div>
 | 
			
		||||
      }
 | 
			
		||||
      return <div style="overflow:hidden">
 | 
			
		||||
         <div class="transition_container" ref={elm => this.mounted = elm}>
 | 
			
		||||
            {this.state.current}
 | 
			
		||||
         </div>
 | 
			
		||||
         {overlay}
 | 
			
		||||
      </div>
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										15
									
								
								src/components/add_button.scss
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										15
									
								
								src/components/add_button.scss
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
// .add_button_container {}
 | 
			
		||||
.add_button_button {
 | 
			
		||||
   position: fixed;
 | 
			
		||||
   bottom: 2rem;
 | 
			
		||||
   right: 2rem;
 | 
			
		||||
   width: 4rem;
 | 
			
		||||
   height: 4rem;
 | 
			
		||||
   padding: 0.75rem;
 | 
			
		||||
   z-index: 16;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.add_button_button>svg {
 | 
			
		||||
   height: 2.5rem;
 | 
			
		||||
   width: 2.5rem;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/components/demo.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										25
									
								
								src/components/demo.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
import { h } from "preact"
 | 
			
		||||
import { Page } from "../page";
 | 
			
		||||
 | 
			
		||||
export interface DemoProps {
 | 
			
		||||
   state: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class DemoPage extends Page<DemoProps, {}> {
 | 
			
		||||
   constructor(props: DemoProps) {
 | 
			
		||||
      super(props);
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      let elms = [];
 | 
			
		||||
      for (let i = 0; i < 1000; i++) {
 | 
			
		||||
         elms.push(<li>Hallo {i}</li>);
 | 
			
		||||
      }
 | 
			
		||||
      return <div style={{ background: this.props.state.color, marginTop: "-12px", paddingTop: "12px" }} >
 | 
			
		||||
         <h1>Hallo Welt</h1>
 | 
			
		||||
         <ul>
 | 
			
		||||
            {elms}
 | 
			
		||||
         </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										40
									
								
								src/components/modals/InputModal.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										40
									
								
								src/components/modals/InputModal.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
import { h, Component } from 'preact';
 | 
			
		||||
import "./modal.scss"
 | 
			
		||||
import { Modal } from './Modal';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class InputModal extends Component<{ title: string, fieldname: string, type: "text" | "password", onResult: (result) => void }, {}> {
 | 
			
		||||
   input: HTMLInputElement;
 | 
			
		||||
   rand: string;
 | 
			
		||||
   constructor(props) {
 | 
			
		||||
      super(props);
 | 
			
		||||
      this.rand = Math.random().toString();
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   componentWillUnmount() {
 | 
			
		||||
      if (this.input)
 | 
			
		||||
         this.input.value = "";
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      return <Modal title={this.props.title} onClose={() => this.props.onResult(null)}>
 | 
			
		||||
         <fieldset style="border:none;">
 | 
			
		||||
            <label for={this.rand}>{this.props.fieldname}</label>
 | 
			
		||||
            <input style="min-width: 85%" autofocus ref={elm => {
 | 
			
		||||
               this.input = elm
 | 
			
		||||
               if (this.input)
 | 
			
		||||
                  setTimeout(() => this.input.focus(), 0)
 | 
			
		||||
            }} type={this.props.type} id={this.rand} placeholder={this.props.fieldname} onKeyDown={evt => {
 | 
			
		||||
               if (evt.keyCode === 13) {
 | 
			
		||||
                  this.props.onResult(this.input.value)
 | 
			
		||||
               }
 | 
			
		||||
            }} />
 | 
			
		||||
            <div style="text-align: right;">
 | 
			
		||||
               <button class="primary" style="display: inline-block;" onClick={() => {
 | 
			
		||||
                  this.props.onResult(this.input.value);
 | 
			
		||||
               }}>Enter</button>
 | 
			
		||||
            </div>
 | 
			
		||||
         </fieldset>
 | 
			
		||||
      </Modal>
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										9
									
								
								src/components/modals/LoadingModal.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								src/components/modals/LoadingModal.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
import { h, Component } from 'preact';
 | 
			
		||||
import "./modal.scss"
 | 
			
		||||
import { Modal } from './Modal';
 | 
			
		||||
 | 
			
		||||
export default function LoadingModal() {
 | 
			
		||||
   return <Modal title="Loading" noClose>
 | 
			
		||||
      <div class="spinner primary" style="height: 80px; width: 80px; margin: 3rem auto;"></div>
 | 
			
		||||
   </Modal>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/components/modals/Modal.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										31
									
								
								src/components/modals/Modal.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
import { h, Component } from 'preact';
 | 
			
		||||
import "./modal.scss"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class Modal extends Component<{ title: string, onClose?: () => void, noClose?: boolean }, {}> {
 | 
			
		||||
   constructor(props) {
 | 
			
		||||
      super(props);
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      return <div class="modal_container" onClick={(evt) => {
 | 
			
		||||
         let path = evt.composedPath();
 | 
			
		||||
         if (!path.find(e => {
 | 
			
		||||
            let res = false;
 | 
			
		||||
            let s = (e as Element);
 | 
			
		||||
            if (s) {
 | 
			
		||||
               if (s.classList) {
 | 
			
		||||
                  res = s.classList.contains("card")
 | 
			
		||||
               }
 | 
			
		||||
            }
 | 
			
		||||
            return res;
 | 
			
		||||
         }))
 | 
			
		||||
            if (this.props.onClose) this.props.onClose();
 | 
			
		||||
      }}>
 | 
			
		||||
         <div class="card" >
 | 
			
		||||
            <h3 class="section">{this.props.title}</h3>
 | 
			
		||||
            {this.props.children}
 | 
			
		||||
         </div>
 | 
			
		||||
      </div>
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/components/modals/YesNoModal.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										39
									
								
								src/components/modals/YesNoModal.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
import { h, Component } from 'preact';
 | 
			
		||||
import "./modal.scss"
 | 
			
		||||
import { Modal } from './Modal';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class YesNoModal extends Component<{ title: string, onResult: (result: boolean | undefined) => void }, {}> {
 | 
			
		||||
   constructor(props) {
 | 
			
		||||
      super(props);
 | 
			
		||||
      this.onKeyDown = this.onKeyDown.bind(this);
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   componentWillMount() {
 | 
			
		||||
      window.addEventListener("keydown", this.onKeyDown);
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   componentWillUnmount() {
 | 
			
		||||
      window.removeEventListener("keydown", this.onKeyDown);
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   onKeyDown(evt: KeyboardEvent) {
 | 
			
		||||
      if (evt.keyCode === 74 || evt.keyCode === 89) this.props.onResult(true)
 | 
			
		||||
      else if (evt.keyCode === 78) this.props.onResult(false)
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      return <Modal title={this.props.title} onClose={() => this.props.onResult(undefined)}>
 | 
			
		||||
         <fieldset style="border:none;">
 | 
			
		||||
            <div style="text-align: right;">
 | 
			
		||||
               <button class="primary" style="display: inline-block;" onClick={() => {
 | 
			
		||||
                  this.props.onResult(false);
 | 
			
		||||
               }}>No</button>
 | 
			
		||||
               <button class="primary" style="display: inline-block;" onClick={() => {
 | 
			
		||||
                  this.props.onResult(true);
 | 
			
		||||
               }}>Yes</button>
 | 
			
		||||
            </div>
 | 
			
		||||
         </fieldset>
 | 
			
		||||
      </Modal>
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										19
									
								
								src/components/modals/modal.scss
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										19
									
								
								src/components/modals/modal.scss
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,19 @@
 | 
			
		||||
.modal_container {
 | 
			
		||||
   position: fixed;
 | 
			
		||||
   top: 0;
 | 
			
		||||
   left: 0;
 | 
			
		||||
   width: 100%;
 | 
			
		||||
   height: 100%;
 | 
			
		||||
   background: rgba(0, 0, 0, 0.8);
 | 
			
		||||
   z-index: 32;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modal_container>.card {
 | 
			
		||||
   position: absolute;
 | 
			
		||||
   left: 0;
 | 
			
		||||
   right: 0;
 | 
			
		||||
   top: 10%;
 | 
			
		||||
   width: 80%;
 | 
			
		||||
   max-width: 800px;
 | 
			
		||||
   margin: auto;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										180
									
								
								src/components/routes/vault/Entry.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										180
									
								
								src/components/routes/vault/Entry.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,180 @@
 | 
			
		||||
import { h, Component } from "preact"
 | 
			
		||||
import Notes, { IVault, ViewNote, MessageType } from "../../../notes";
 | 
			
		||||
import Trash from "feather-icons/dist/icons/trash-2.svg"
 | 
			
		||||
import X from "feather-icons/dist/icons/x.svg"
 | 
			
		||||
import Save from "feather-icons/dist/icons/save.svg"
 | 
			
		||||
 | 
			
		||||
import Navigation from "../../../navigation";
 | 
			
		||||
import { YesNoModal } from "../../modals/YesNoModal";
 | 
			
		||||
import LoadingModal from "../../modals/LoadingModal";
 | 
			
		||||
 | 
			
		||||
const minRows = 3;
 | 
			
		||||
export default class EntryComponent extends Component<{ vault: Promise<IVault>, id: string | undefined }, { loading: boolean, title: string, changed: boolean, modal: JSX.Element | undefined }> {
 | 
			
		||||
   old_text: string;
 | 
			
		||||
   text: string = "";
 | 
			
		||||
   vault: IVault;
 | 
			
		||||
   lineHeight: number = 24;
 | 
			
		||||
   note: ViewNote;
 | 
			
		||||
 | 
			
		||||
   rows: number = minRows;
 | 
			
		||||
 | 
			
		||||
   skip_save: boolean = false;
 | 
			
		||||
 | 
			
		||||
   constructor(props) {
 | 
			
		||||
      super(props);
 | 
			
		||||
      this.state = { changed: false, title: "", modal: undefined, loading: true };
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   private toVault() {
 | 
			
		||||
      history.back()
 | 
			
		||||
      // Navigation.setPage("/vault", { id: this.vault.id }, { entry: "false" }, true);
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   async componentWillMount() {
 | 
			
		||||
      try {
 | 
			
		||||
         this.skip_save = false;
 | 
			
		||||
         this.setState({ loading: true })
 | 
			
		||||
         this.vault = await this.props.vault;
 | 
			
		||||
         let note: ViewNote;
 | 
			
		||||
         if (this.props.id)
 | 
			
		||||
            note = await this.vault.getNote(this.props.id)
 | 
			
		||||
         else
 | 
			
		||||
            note = this.vault.newNote();
 | 
			
		||||
 | 
			
		||||
         if (!note) {
 | 
			
		||||
            Notes.messageObservableServer.send({ message: "Note not found!", type: MessageType.ERROR });
 | 
			
		||||
            // this.toVault()
 | 
			
		||||
         } 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({ loading: false, title })
 | 
			
		||||
         }
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
         Notes.sendErrorMessage(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) {
 | 
			
		||||
         Notes.sendErrorMessage(err);
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   // async onKeypress(event) {
 | 
			
		||||
   //    event = event || window.event;
 | 
			
		||||
   //    
 | 
			
		||||
   // }
 | 
			
		||||
 | 
			
		||||
   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
 | 
			
		||||
      let target = (evt.target as HTMLTextAreaElement)
 | 
			
		||||
      let value = target.value;
 | 
			
		||||
 | 
			
		||||
      this.text = value;
 | 
			
		||||
      if (!this.state.changed && this.textAreaKeyPress(evt)) this.setState({ changed: true })
 | 
			
		||||
 | 
			
		||||
      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;
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   exitHandler() {
 | 
			
		||||
      if (this.state.changed) {
 | 
			
		||||
         let modal = <YesNoModal title="Really want to quit?" onResult={res => {
 | 
			
		||||
            if (res === true) {
 | 
			
		||||
               this.skip_save = true;
 | 
			
		||||
               this.toVault();
 | 
			
		||||
            }
 | 
			
		||||
            this.setState({ modal: undefined });
 | 
			
		||||
         }} />
 | 
			
		||||
         this.setState({ modal })
 | 
			
		||||
      } else
 | 
			
		||||
         this.toVault()
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   textAreaKeyPress(evt: KeyboardEvent) {
 | 
			
		||||
      if ((evt.keyCode === 83 || evt.keyCode === 13) && evt.ctrlKey) {
 | 
			
		||||
         event.preventDefault()
 | 
			
		||||
         this.save();
 | 
			
		||||
         return false;
 | 
			
		||||
      }
 | 
			
		||||
      else if (evt.keyCode === 27) {
 | 
			
		||||
         event.preventDefault();
 | 
			
		||||
         // this.skip_save = true;
 | 
			
		||||
         // this.toVault()
 | 
			
		||||
         this.exitHandler();
 | 
			
		||||
         return false;
 | 
			
		||||
      }
 | 
			
		||||
      return true;
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      let loading_modal = this.state.loading ? <LoadingModal /> : undefined;
 | 
			
		||||
 | 
			
		||||
      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()
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return <div>
 | 
			
		||||
         {loading_modal}
 | 
			
		||||
         {this.state.modal}
 | 
			
		||||
         <header>
 | 
			
		||||
            <div>
 | 
			
		||||
               <a class="button header_icon_button" onClick={() => this.exitHandler()}><X height={undefined} width={undefined} /></a>
 | 
			
		||||
               {this.state.changed ? <a class="button header_icon_button" onClick={() => save_handler()}><Save height={undefined} width={undefined} /></a> : undefined}
 | 
			
		||||
            </div>
 | 
			
		||||
            <h1 style="display:inline" class="button header_title">{this.state.title}</h1>
 | 
			
		||||
            <a class="button header_icon_button" onClick={() => delete_handler()}><Trash height={undefined} width={undefined} /></a>
 | 
			
		||||
         </header>
 | 
			
		||||
         <div class="container">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
               <div class="col-sm-12 col-md-8 col-lg-6 col-md-offset-2 col-lg-offset-3">
 | 
			
		||||
                  <textarea autofocus value={this.text} rows={this.rows} class="doc" style="width:100%;" onKeyDown={evt => this.textAreaKeyPress(evt)} onKeyUp={evt => this.textAreaChange(evt)} ref={elm => {
 | 
			
		||||
                     if (elm)
 | 
			
		||||
                        setTimeout(() => elm.focus(), 0)
 | 
			
		||||
                  }} />
 | 
			
		||||
               </div>
 | 
			
		||||
            </div>
 | 
			
		||||
         </div>
 | 
			
		||||
      </div>
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										57
									
								
								src/components/routes/vault/EntryList.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										57
									
								
								src/components/routes/vault/EntryList.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,57 @@
 | 
			
		||||
import { h, Component } from "preact"
 | 
			
		||||
import { IVault, BaseNote } from "../../../notes";
 | 
			
		||||
import AddButton from "../../AddButton";
 | 
			
		||||
import Navigation from "../../../navigation";
 | 
			
		||||
import ArrowLeft from "feather-icons/dist/icons/arrow-left.svg"
 | 
			
		||||
import MoreVertival from "feather-icons/dist/icons/more-vertical.svg"
 | 
			
		||||
 | 
			
		||||
export default class EntryList extends Component<{ vault: Promise<IVault> }, { entries: BaseNote[] }> {
 | 
			
		||||
   constructor(props) {
 | 
			
		||||
      super(props)
 | 
			
		||||
      this.state = { entries: [] }
 | 
			
		||||
   }
 | 
			
		||||
   vault: IVault;
 | 
			
		||||
   async componentWillMount() {
 | 
			
		||||
      this.vault = await this.props.vault;
 | 
			
		||||
      this.vault.getAllNotes().then(entries => this.setState({ entries }))
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      const open_entry = (id: string | null) => {
 | 
			
		||||
         Navigation.setPage("/vault", { id: this.vault.id }, { id, entry: "true" })
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let elms = this.state.entries.map(entry => {
 | 
			
		||||
         let [first, second] = entry.preview.split("\n", 2);
 | 
			
		||||
         return <div class="vault_vault" onClick={() => {
 | 
			
		||||
            open_entry(entry._id)
 | 
			
		||||
         }}>
 | 
			
		||||
            <span>{first}</span><br />
 | 
			
		||||
            <span>{second}</span>
 | 
			
		||||
         </div>
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      return <div>
 | 
			
		||||
         <header>
 | 
			
		||||
            <div>
 | 
			
		||||
               <a class="button header_icon_button" onClick={() => history.back()}><ArrowLeft height={undefined} width={undefined} /></a>
 | 
			
		||||
            </div>
 | 
			
		||||
            <h1 style="display:inline" class="button header_title" onClick={() => Navigation.setPage("/")}>{this.vault ? this.vault.name : ""}</h1>
 | 
			
		||||
            <a class="button header_icon_button"><MoreVertival height={undefined} width={undefined} /></a>
 | 
			
		||||
         </header>
 | 
			
		||||
         <AddButton onClick={() => open_entry(null)} />
 | 
			
		||||
         <div class="container">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
               <div class="col-sm-12 col-md-8 col-lg-6 col-md-offset-2 col-lg-offset-3">
 | 
			
		||||
                  <div class="card fluid">
 | 
			
		||||
                     <h1 class="section double-padded">Notes: </h1>
 | 
			
		||||
                     <div class="section">
 | 
			
		||||
                        {elms}
 | 
			
		||||
                     </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
               </div>
 | 
			
		||||
            </div>
 | 
			
		||||
         </div>
 | 
			
		||||
      </div>
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/components/routes/vault/Vault.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										37
									
								
								src/components/routes/vault/Vault.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
import { h } from "preact"
 | 
			
		||||
import { Page } from "../../../page";
 | 
			
		||||
import Notes, { IVault, BaseNote } from "../../../notes";
 | 
			
		||||
import Navigation from "../../../navigation";
 | 
			
		||||
import EntryComponent from "./Entry";
 | 
			
		||||
import EntryList from "./EntryList";
 | 
			
		||||
 | 
			
		||||
import "./vault.scss"
 | 
			
		||||
 | 
			
		||||
export interface VaultProps {
 | 
			
		||||
   state: { id: string };
 | 
			
		||||
   hidden: { entry: string, id: string };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class VaultPage extends Page<VaultProps, { entries: BaseNote[] }> {
 | 
			
		||||
   vault: Promise<IVault>
 | 
			
		||||
   constructor(props: VaultProps) {
 | 
			
		||||
      super(props);
 | 
			
		||||
      this.state = { entries: [] };
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   async componentWillMount() {
 | 
			
		||||
      this.vault = Notes.getVault(this.props.state.id, Notes.getVaultKey(this.props.state.id))
 | 
			
		||||
 | 
			
		||||
      this.vault.catch(err => {
 | 
			
		||||
         Navigation.setPage("/")
 | 
			
		||||
      })
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      if (this.props.hidden && this.props.hidden.entry === "true") {
 | 
			
		||||
         return <EntryComponent vault={this.vault} id={this.props.hidden.id} />
 | 
			
		||||
      } else {
 | 
			
		||||
         return <EntryList vault={this.vault} />
 | 
			
		||||
      }
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/components/routes/vault/vault.scss
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										27
									
								
								src/components/routes/vault/vault.scss
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,27 @@
 | 
			
		||||
.vault_vault>span:nth-of-type(1) {
 | 
			
		||||
   font-size: 1.3rem;
 | 
			
		||||
   margin-left: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vault_vault>span {
 | 
			
		||||
   font-size: 1rem;
 | 
			
		||||
   margin-left: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vault_vault {
 | 
			
		||||
   padding: 0.5rem;
 | 
			
		||||
   border-bottom: solid 1px var(--fore-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vault_vault:hover {
 | 
			
		||||
   background: var(--nav-hover-back-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vault_vault>svg {
 | 
			
		||||
   height: 2rem;
 | 
			
		||||
   margin-right: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vault_vault:last-child {
 | 
			
		||||
   border-bottom: none;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										126
									
								
								src/components/routes/vaults/Vaults.tsx
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										126
									
								
								src/components/routes/vaults/Vaults.tsx
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,126 @@
 | 
			
		||||
import { h } from "preact"
 | 
			
		||||
import { Page } from "../../../page";
 | 
			
		||||
import Notes, { VaultList } from "../../../notes";
 | 
			
		||||
import "./vaults.scss"
 | 
			
		||||
import Lock from "feather-icons/dist/icons/lock.svg";
 | 
			
		||||
import Unlock from "feather-icons/dist/icons/unlock.svg";
 | 
			
		||||
import Navigation from "../../../navigation";
 | 
			
		||||
import { InputModal } from "../../modals/InputModal";
 | 
			
		||||
import { YesNoModal } from "../../modals/YesNoModal";
 | 
			
		||||
import AddButton from "../../AddButton";
 | 
			
		||||
 | 
			
		||||
export interface VaultsProps {
 | 
			
		||||
   state: any;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default class VaultsPage extends Page<VaultsProps, { vaults: VaultList, modal: JSX.Element | undefined }> {
 | 
			
		||||
   constructor(props: VaultsProps) {
 | 
			
		||||
      super(props);
 | 
			
		||||
      this.state = { vaults: [], modal: undefined };
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   updateVaults() {
 | 
			
		||||
      Notes.getVaults().then(vaults => this.setState({ vaults }))
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   componentWillMount() {
 | 
			
		||||
      this.updateVaults()
 | 
			
		||||
   }
 | 
			
		||||
 | 
			
		||||
   render() {
 | 
			
		||||
      let elms = this.state.vaults.map(vault => {
 | 
			
		||||
         return <div class="vaults_vault" onClick={() => {
 | 
			
		||||
            const open = () => {
 | 
			
		||||
               Navigation.setPage("/vault", { id: vault.id })
 | 
			
		||||
            }
 | 
			
		||||
            if (vault.encrypted) {
 | 
			
		||||
               let key = Notes.getVaultKey(vault.id);
 | 
			
		||||
               if (key) open()
 | 
			
		||||
               else {
 | 
			
		||||
                  let modal = <InputModal title={"Enter password for " + vault.name} type="password" fieldname="Password" onResult={(value) => {
 | 
			
		||||
                     if (value === null) this.setState({ modal: undefined });
 | 
			
		||||
                     else {
 | 
			
		||||
                        let key = Notes.passwordToKey(value);
 | 
			
		||||
                        Notes.getVault(vault.id, key).then(() => {
 | 
			
		||||
                           let modal = <YesNoModal title="Save permanent?" onResult={(res) => {
 | 
			
		||||
                              if (res === undefined)
 | 
			
		||||
                                 this.setState({ modal: undefined });
 | 
			
		||||
                              else {
 | 
			
		||||
                                 this.setState({ modal: undefined });
 | 
			
		||||
                                 Notes.saveVaultKey(vault.id, key, res);
 | 
			
		||||
                                 open()
 | 
			
		||||
                              }
 | 
			
		||||
                           }} />
 | 
			
		||||
                           this.setState({ modal })
 | 
			
		||||
                        }).catch(err => {
 | 
			
		||||
                           alert("Invalid password!")
 | 
			
		||||
                        })
 | 
			
		||||
                     }
 | 
			
		||||
                  }} />
 | 
			
		||||
                  this.setState({ modal })
 | 
			
		||||
               }
 | 
			
		||||
            } else {
 | 
			
		||||
               open()
 | 
			
		||||
            }
 | 
			
		||||
         }}>
 | 
			
		||||
            {vault.encrypted ? <Lock height={undefined} width={undefined} /> : <Unlock height={undefined} width={undefined} />}
 | 
			
		||||
            <span>
 | 
			
		||||
               {vault.name}
 | 
			
		||||
            </span>
 | 
			
		||||
         </div>
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      return <div style={{ marginTop: "-12px", paddingTop: "12px" }} >
 | 
			
		||||
         {this.state.modal}
 | 
			
		||||
         <AddButton onClick={() => {
 | 
			
		||||
 | 
			
		||||
            let modal = <InputModal title="Enter new name" fieldname="Name" type="text" onResult={(name) => {
 | 
			
		||||
               const create = async (password?: string) => {
 | 
			
		||||
                  let key;
 | 
			
		||||
                  if (password) {
 | 
			
		||||
                     key = Notes.passwordToKey(password)
 | 
			
		||||
                  }
 | 
			
		||||
                  await Notes.createVault(name, key)
 | 
			
		||||
                  this.updateVaults();
 | 
			
		||||
               }
 | 
			
		||||
 | 
			
		||||
               if (name === null) this.setState({ modal: undefined })
 | 
			
		||||
               else {
 | 
			
		||||
                  let modal = <YesNoModal title="Encrypt?" onResult={(encrypted) => {
 | 
			
		||||
                     if (encrypted === null) this.setState({ modal: undefined })
 | 
			
		||||
                     if (encrypted) {
 | 
			
		||||
                        let modal = <InputModal title="Enter new password" fieldname="Password" type="password" onResult={(password) => {
 | 
			
		||||
                           if (password === null) this.setState({ modal: undefined })
 | 
			
		||||
                           else {
 | 
			
		||||
                              create(password)
 | 
			
		||||
                              this.setState({ modal: undefined });
 | 
			
		||||
                           }
 | 
			
		||||
                        }} />
 | 
			
		||||
                        this.setState({ modal })
 | 
			
		||||
                     } else {
 | 
			
		||||
                        create()
 | 
			
		||||
                        this.setState({ modal: undefined });
 | 
			
		||||
                     }
 | 
			
		||||
                  }} />
 | 
			
		||||
                  this.setState({ modal })
 | 
			
		||||
               }
 | 
			
		||||
            }} />
 | 
			
		||||
            this.setState({ modal })
 | 
			
		||||
         }} />
 | 
			
		||||
         <div class="container">
 | 
			
		||||
            <div class="row">
 | 
			
		||||
               <div class="col-sm-12 col-md-8 col-lg-6 col-md-offset-2 col-lg-offset-3">
 | 
			
		||||
                  <div class="card fluid">
 | 
			
		||||
 | 
			
		||||
                     <h1 class="section double-padded">Your vaults:</h1>
 | 
			
		||||
 | 
			
		||||
                     <div class="section">
 | 
			
		||||
                        {elms}
 | 
			
		||||
                     </div>
 | 
			
		||||
                  </div>
 | 
			
		||||
               </div>
 | 
			
		||||
            </div>
 | 
			
		||||
         </div>
 | 
			
		||||
      </div>
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/components/routes/vaults/vaults.scss
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										21
									
								
								src/components/routes/vaults/vaults.scss
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
.vaults_vault>span {
 | 
			
		||||
   font-size: 2rem !important;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vaults_vault {
 | 
			
		||||
   padding: 0.5rem;
 | 
			
		||||
   border-bottom: solid 1px var(--fore-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vaults_vault:hover {
 | 
			
		||||
   background: var(--nav-hover-back-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vaults_vault>svg {
 | 
			
		||||
   height: 2rem;
 | 
			
		||||
   margin-right: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.vaults_vault:last-child {
 | 
			
		||||
   border-bottom: none;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										24
									
								
								src/components/routing.scss
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								src/components/routing.scss
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
.transition_container {
 | 
			
		||||
   position: absolute;
 | 
			
		||||
   top: 0;
 | 
			
		||||
   left: 0;
 | 
			
		||||
   height: 100%;
 | 
			
		||||
   width: 100%;
 | 
			
		||||
   overflow: auto;
 | 
			
		||||
   background: var(--back-color);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.transition_slidein {
 | 
			
		||||
   animation-name: slidein;
 | 
			
		||||
   animation-duration: 0.3s;
 | 
			
		||||
   z-index: 128;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@keyframes slidein {
 | 
			
		||||
   from {
 | 
			
		||||
      left: 100%;
 | 
			
		||||
   }
 | 
			
		||||
   to {
 | 
			
		||||
      left: 0%;
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user