fastapi = "*"
uvicorn = {extras = ["standard"], version = "*"}
aiofiles = "*"
+python-jose = {extras = ["cryptography"], version = "*"}
[requires]
python_version = "3.8"
{
"_meta": {
"hash": {
- "sha256": "7bdbb656d72c9dd9bed01397a477e2f03880651d32fcbcae6aa0b5992f6c9da3"
+ "sha256": "f03f2187cd2a303a19231321028d98b5a755f2a20ff2601ce7b4739cfe9d5ae4"
},
"pipfile-spec": 6,
"requires": {
"markers": "python_version >= '3.6'",
"version": "==3.4.1"
},
+ "cffi": {
+ "hashes": [
+ "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d",
+ "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771",
+ "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872",
+ "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c",
+ "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc",
+ "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762",
+ "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202",
+ "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5",
+ "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548",
+ "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a",
+ "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f",
+ "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20",
+ "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218",
+ "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c",
+ "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e",
+ "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56",
+ "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224",
+ "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a",
+ "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2",
+ "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a",
+ "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819",
+ "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346",
+ "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b",
+ "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e",
+ "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534",
+ "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb",
+ "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0",
+ "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156",
+ "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd",
+ "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87",
+ "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc",
+ "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195",
+ "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33",
+ "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f",
+ "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d",
+ "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd",
+ "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728",
+ "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7",
+ "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca",
+ "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99",
+ "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf",
+ "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e",
+ "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c",
+ "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5",
+ "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69"
+ ],
+ "version": "==1.14.6"
+ },
"click": {
"hashes": [
"sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a",
],
"version": "==0.4.4"
},
+ "cryptography": {
+ "hashes": [
+ "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d",
+ "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959",
+ "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6",
+ "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873",
+ "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2",
+ "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713",
+ "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1",
+ "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177",
+ "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250",
+ "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586",
+ "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3",
+ "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca",
+ "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d",
+ "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"
+ ],
+ "version": "==3.4.7"
+ },
+ "ecdsa": {
+ "hashes": [
+ "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676",
+ "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"
+ ],
+ "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==0.17.0"
+ },
"fastapi": {
"hashes": [
"sha256:c9256a89b0436223b45f53fe3a39b178f3da6be5841a2c59deedff4b676d003f",
],
"version": "==0.2.0"
},
+ "pyasn1": {
+ "hashes": [
+ "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
+ "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
+ "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
+ "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
+ "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
+ "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
+ "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
+ "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
+ "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
+ "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
+ "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
+ "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2",
+ "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3"
+ ],
+ "version": "==0.4.8"
+ },
+ "pycparser": {
+ "hashes": [
+ "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
+ "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==2.20"
+ },
"pydantic": {
"hashes": [
"sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd",
],
"version": "==0.19.0"
},
+ "python-jose": {
+ "extras": [
+ "cryptography"
+ ],
+ "hashes": [
+ "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a",
+ "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"
+ ],
+ "index": "pypi",
+ "version": "==3.3.0"
+ },
"pyyaml": {
"hashes": [
"sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf",
],
"version": "==5.4.1"
},
+ "rsa": {
+ "hashes": [
+ "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2",
+ "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9"
+ ],
+ "markers": "python_version >= '3.5' and python_version < '4'",
+ "version": "==4.7.2"
+ },
+ "six": {
+ "hashes": [
+ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
+ "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
+ ],
+ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
+ "version": "==1.16.0"
+ },
"starlette": {
"hashes": [
"sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed",
"standard"
],
"hashes": [
- "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae",
- "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292"
+ "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1",
+ "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff"
],
"index": "pypi",
- "version": "==0.14.0"
+ "version": "==0.15.0"
},
"watchgod": {
"hashes": [
},
"tomli": {
"hashes": [
- "sha256:056f0376bf5a6b182c513f9582c1e5b0487265eb6c48842b69aa9ca1cd5f640a",
- "sha256:d60e681734099207a6add7a10326bc2ddd1fdc36c1b0f547d00ef73ac63739c2"
+ "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f",
+ "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"
],
"markers": "python_version >= '3.6'",
- "version": "==1.2.0"
+ "version": "==1.2.1"
}
}
}
* [x] Proof of Concept - Stage 1
* [x] Notes List
* [x] Full Text Searching
-* [ ] Proof of Concept - Stage 2
+* [x] Proof of Concept - Stage 2
* [x] View Note Content
* [x] Edit Note Content
* [x] Docker Deployment
- * [ ] Password Authentication
+ * [x] Password Authentication
* [ ] Proof of Concept - Stage 3
+ * [ ] Public URL Sharing
+* [ ] Proof of Concept - Stage 4
* [ ] Image Embedding
* [ ] Attachment Upload
-* [ ] Proof of Concept - Stage 4
- * [ ] Public URL Sharing
* [ ] First Release
* [ ] Clean & Responsive UI
* [ ] Ability to Create a Note
container_name: flatnotes
image: dullage/flatnotes:latest
build: .
+ environment:
+ FLATNOTES_USERNAME: "user"
+ FLATNOTES_PASSWORD: "changeMe!"
+ FLATNOTES_SECRET_KEY: "aLongRandomSeriesOfCharacters"
+ # FLATNOTES_SESSION_EXPIRY_DAYS: "7" # Optional. Defaults to 30.
volumes:
- "./data:/data"
- # - "./index:/data/.flatnotes" # Optional
+ # - "./index:/data/.flatnotes" # Optional.
ports:
- "80:80"
restart: unless-stopped
--- /dev/null
+import os
+from datetime import datetime, timedelta
+
+from fastapi import Depends, HTTPException
+from fastapi.security import OAuth2PasswordBearer
+from jose import JWTError, jwt
+
+FLATNOTES_USERNAME = os.environ["FLATNOTES_USERNAME"]
+FLATNOTES_PASSWORD = os.environ["FLATNOTES_PASSWORD"]
+
+JWT_SECRET_KEY = os.environ["FLATNOTES_SECRET_KEY"]
+JWT_EXPIRE_DAYS = int(os.environ.get("FLATNOTES_SESSION_EXPIRY_DAYS", 30))
+JWT_ALGORITHM = "HS256"
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/token")
+
+
+def create_access_token(data: dict):
+ to_encode = data.copy()
+ expiry_datetime = datetime.utcnow() + timedelta(days=JWT_EXPIRE_DAYS)
+ to_encode.update({"exp": expiry_datetime})
+ encoded_jwt = jwt.encode(
+ to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM
+ )
+ return encoded_jwt
+
+
+async def validate_token(token: str = Depends(oauth2_scheme)):
+ try:
+ payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM])
+ username = payload.get("sub")
+ if username is None or username.lower() != FLATNOTES_USERNAME.lower():
+ raise ValueError
+ return FLATNOTES_USERNAME
+ except:
+ raise HTTPException(
+ status_code=401,
+ detail="Invalid authentication credentials",
+ headers={"WWW-Authenticate": "Bearer"},
+ )
import logging
import os
-from typing import Dict, List, Optional
-
-from fastapi import FastAPI
-from fastapi.responses import HTMLResponse
-from fastapi.staticfiles import StaticFiles
+from typing import List
+from auth import (
+ FLATNOTES_PASSWORD,
+ FLATNOTES_USERNAME,
+ validate_token,
+ create_access_token,
+)
from error_responses import (
file_exists_response,
file_not_found_response,
filename_contains_path_response,
)
-from flatnotes import FilenameContainsPathError, Flatnotes, Note, NoteHit
-from helpers import CamelCaseBaseModel
+from fastapi import Depends, FastAPI, HTTPException
+from fastapi.responses import HTMLResponse
+from fastapi.staticfiles import StaticFiles
+from models import LoginModel, NoteHitModel, NoteModel, NotePatchModel
+
+from flatnotes import FilenameContainsPathError, Flatnotes, Note
logging.basicConfig(
format="%(asctime)s [%(levelname)s]: %(message)s",
logger.setLevel(os.environ.get("LOGLEVEL", "INFO").upper())
app = FastAPI()
-
flatnotes = Flatnotes(os.environ["FLATNOTES_PATH"])
-class NoteModel(CamelCaseBaseModel):
- filename: str
- last_modified: int
- content: Optional[str]
-
- @classmethod
- def dump(cls, note: Note, include_content: bool = True) -> Dict:
- return {
- "filename": note.filename,
- "lastModified": note.last_modified,
- "content": note.content if include_content else None,
- }
-
-
-class NotePatchModel(CamelCaseBaseModel):
- new_filename: Optional[str]
- new_content: Optional[str]
-
-
-class NoteHitModel(CamelCaseBaseModel):
- filename: str
- last_modified: int
- title_highlights: Optional[str]
- content_highlights: Optional[str]
-
- @classmethod
- def dump(self, note_hit: NoteHit) -> Dict:
- return {
- "filename": note_hit.filename,
- "lastModified": note_hit.last_modified,
- "titleHighlights": note_hit.title_highlights,
- "contentHighlights": note_hit.content_highlights,
- }
+@app.post("/api/token")
+async def token(data: LoginModel):
+ if (
+ data.username != FLATNOTES_USERNAME
+ or data.password != FLATNOTES_PASSWORD
+ ):
+ raise HTTPException(
+ status_code=400, detail="Incorrect username or password"
+ )
+ access_token = create_access_token(data={"sub": FLATNOTES_USERNAME})
+ return {"access_token": access_token, "token_type": "bearer"}
@app.get("/")
@app.get("/api/notes", response_model=List[NoteModel])
-async def get_notes(include_content: bool = False):
+async def get_notes(
+ include_content: bool = False, _: str = Depends(validate_token)
+):
"""Get all notes."""
return [
NoteModel.dump(note, include_content=include_content)
@app.post("/api/notes", response_model=NoteModel)
-async def post_note(filename: str, content: str):
+async def post_note(
+ filename: str, content: str, _: str = Depends(validate_token)
+):
"""Create a new note."""
try:
note = Note(flatnotes, filename, new=True)
@app.get("/api/notes/{filename}", response_model=NoteModel)
-async def get_note(filename: str, include_content: bool = True):
+async def get_note(
+ filename: str,
+ include_content: bool = True,
+ _: str = Depends(validate_token),
+):
"""Get a specific note."""
try:
note = Note(flatnotes, filename)
@app.patch("/api/notes/{filename}", response_model=NoteModel)
-async def patch_note(filename: str, new_data: NotePatchModel):
+async def patch_note(
+ filename: str, new_data: NotePatchModel, _: str = Depends(validate_token)
+):
try:
note = Note(flatnotes, filename)
if new_data.new_filename is not None:
@app.delete("/api/notes/{filename}")
-async def delete_note(filename: str):
+async def delete_note(filename: str, _: str = Depends(validate_token)):
try:
note = Note(flatnotes, filename)
note.delete()
@app.get("/api/search", response_model=List[NoteHitModel])
-async def search(term: str):
+async def search(term: str, _: str = Depends(validate_token)):
"""Perform a full text search for a note."""
return [NoteHitModel.dump(note_hit) for note_hit in flatnotes.search(term)]
--- /dev/null
+from typing import Dict, Optional
+
+from helpers import CamelCaseBaseModel
+
+from flatnotes import Note, NoteHit
+
+
+class LoginModel(CamelCaseBaseModel):
+ username: str
+ password: str
+
+
+class NoteModel(CamelCaseBaseModel):
+ filename: str
+ last_modified: int
+ content: Optional[str]
+
+ @classmethod
+ def dump(cls, note: Note, include_content: bool = True) -> Dict:
+ return {
+ "filename": note.filename,
+ "lastModified": note.last_modified,
+ "content": note.content if include_content else None,
+ }
+
+
+class NotePatchModel(CamelCaseBaseModel):
+ new_filename: Optional[str]
+ new_content: Optional[str]
+
+
+class NoteHitModel(CamelCaseBaseModel):
+ filename: str
+ last_modified: int
+ title_highlights: Optional[str]
+ content_highlights: Optional[str]
+
+ @classmethod
+ def dump(self, note_hit: NoteHit) -> Dict:
+ return {
+ "filename": note_hit.filename,
+ "lastModified": note_hit.last_modified,
+ "titleHighlights": note_hit.title_highlights,
+ "contentHighlights": note_hit.content_highlights,
+ }
--- /dev/null
+import axios from "axios";
+import EventBus from "./eventBus.js";
+
+const api = axios.create();
+
+api.interceptors.request.use(
+ function(config) {
+ if (config.url !== "/api/token") {
+ let token = sessionStorage.getItem("token");
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+ },
+ function(error) {
+ return Promise.reject(error);
+ }
+);
+
+api.interceptors.response.use(
+ function(response) {
+ return response;
+ },
+ function(error) {
+ if (
+ typeof error.response !== "undefined" &&
+ error.response.status === 401
+ ) {
+ EventBus.$emit("logout");
+ }
+ return Promise.reject(error);
+ }
+);
+
+export default api;
import "@toast-ui/editor/dist/toastui-editor-viewer.css";
import { Editor } from "@toast-ui/vue-editor";
import { Viewer } from "@toast-ui/vue-editor";
-import axios from "axios";
+import api from "../api";
import { Note, SearchResult } from "./classes";
+import EventBus from "../eventBus";
export default {
components: {
data: function() {
return {
+ loggedIn: false,
+ usernameInput: null,
+ passwordInput: null,
+ rememberMeInput: false,
notes: [],
searchTerm: null,
searchTimeout: null,
computed: {
currentView: function() {
+ // 4 - Login
+ if (this.loggedIn == false) {
+ return 4;
+ }
// 3 - Edit Note
- if (this.currentNote && this.editMode) {
+ else if (this.currentNote && this.editMode) {
return 3;
}
// 2 - View Note
},
methods: {
+ login: function() {
+ let parent = this;
+ api
+ .post("/api/token", {
+ username: this.usernameInput,
+ password: this.passwordInput,
+ })
+ .then(function(response) {
+ sessionStorage.setItem("token", response.data.access_token);
+ if (parent.rememberMeInput == true) {
+ localStorage.setItem("token", response.data.access_token);
+ }
+ parent.loggedIn = true;
+ parent.getNotes();
+ })
+ .finally(function() {
+ parent.usernameInput = null;
+ parent.passwordInput = null;
+ parent.rememberMeInput = false;
+ });
+ },
+
+ logout: function() {
+ sessionStorage.removeItem("token");
+ localStorage.removeItem("token");
+ this.loggedIn = false;
+ },
+
getNotes: function() {
- parent = this;
+ let parent = this;
parent.notes = [];
- axios.get("/api/notes").then(function(response) {
+ api.get("/api/notes").then(function(response) {
response.data.forEach(function(note) {
parent.notes.push(new Note(note.filename, note.lastModified));
});
});
},
- clearSearchTimeout: function(params) {
+ clearSearchTimeout: function() {
if (this.searchTimeout != null) {
clearTimeout(this.searchTimeout);
}
},
search: function() {
- parent = this;
+ let parent = this;
this.clearSearchTimeout();
- this.searchResults = [];
if (this.searchTerm) {
- axios
+ api
.get("/api/search", { params: { term: this.searchTerm } })
.then(function(response) {
+ parent.searchResults = [];
response.data.forEach(function(result) {
parent.searchResults.push(
new SearchResult(
},
loadNote: function(filename) {
- parent = this;
- axios.get(`/api/notes/${filename}`).then(function(response) {
+ let parent = this;
+ api.get(`/api/notes/${filename}`).then(function(response) {
parent.currentNote = response.data;
});
},
},
saveNote: function() {
- parent = this;
+ let parent = this;
let newContent = this.$refs.toastUiEditor.invoke("getMarkdown");
if (newContent != this.currentNote.content) {
- axios
+ api
.patch(`/api/notes/${this.currentNote.filename}`, {
newContent: newContent,
})
},
created: function() {
- this.getNotes();
+ EventBus.$on("logout", this.logout);
+
+ let token = localStorage.getItem("token");
+ if (token != null) {
+ sessionStorage.setItem("token", token);
+ this.loggedIn = true;
+ this.getNotes();
+ }
},
};
</div>
<!-- Buttons -->
- <div
- v-if="currentView == 2 || currentView == 3"
- class="d-flex justify-content-center mb-4"
- >
+ <div v-if="currentView != 4" class="d-flex justify-content-center mb-4">
+ <!-- Logout -->
+ <button
+ v-if="currentView == 0"
+ type="button"
+ class="btn btn-light mx-1"
+ @click="logout"
+ >
+ Logout
+ </button>
+
<!-- Close -->
<button
v-if="currentView == 2"
type="button"
- class="btn btn-secondary"
+ class="btn btn-secondary mx-1"
@click="unloadNote"
>
Close
<button
v-if="currentView == 2"
type="button"
- class="btn btn-warning ms-2"
+ class="btn btn-warning mx-1"
@click="editMode = true"
>
Edit
<button
v-if="currentView == 3"
type="button"
- class="btn btn-secondary ms-2"
+ class="btn btn-secondary mx-1"
@click="editMode = false"
>
Cancel
<button
v-if="currentView == 3"
type="button"
- class="btn btn-success ms-2"
+ class="btn btn-success mx-1"
@click="saveNote"
>
Save
</button>
</div>
+ <!-- Login -->
+ <div v-if="currentView == 4" class="d-flex justify-content-center">
+ <form v-on:submit.prevent="login">
+ <div class="mb-3">
+ <label for="username" class="form-label">Username</label>
+ <input
+ type="text"
+ class="form-control"
+ id="username"
+ autocomplete="username"
+ v-model="usernameInput"
+ />
+ </div>
+ <div class="mb-3">
+ <label for="password" class="form-label">Password</label>
+ <input
+ type="password"
+ class="form-control"
+ id="password"
+ autocomplete="current-password"
+ v-model="passwordInput"
+ />
+ </div>
+ <div class="mb-3 form-check">
+ <input
+ type="checkbox"
+ class="form-check-input"
+ id="rememberMe"
+ v-model="rememberMeInput"
+ />
+ <label class="form-check-label" for="rememberMe">Remember Me</label>
+ </div>
+ <button type="submit" class="btn btn-primary">Log In</button>
+ </form>
+ </div>
+
<!-- Viewer -->
- <div v-if="currentView == 2">
+ <div v-else-if="currentView == 2">
<viewer :initialValue="currentNote.content" height="600px" />
</div>
<!-- Front Page -->
<div v-else>
<!-- Search Input -->
- <div class="form-group mb-4 d-flex justify-content-center">
- <input
- type="text"
- class="form-control"
- placeholder="Search"
- v-model="searchTerm"
- @change="search"
- style="max-width: 500px"
- />
- </div>
+ <form v-on:submit.prevent="search">
+ <div class="form-group mb-4 d-flex justify-content-center">
+ <input
+ type="text"
+ class="form-control"
+ placeholder="Search"
+ v-model="searchTerm"
+ style="max-width: 500px"
+ />
+ </div>
+ </form>
<!-- Search Results -->
<div v-if="currentView == 1">
--- /dev/null
+import Vue from "vue";
+
+export default new Vue();