Compare commits

..

No commits in common. "master" and "note-repo" have entirely different histories.

40 changed files with 292 additions and 686 deletions

View file

@ -1,13 +1,4 @@
# 🚀 Getting started with PomeloNote
### **THIS REPOSITORY HAS DEPENDENCIES WITH SECURITY VULNERABILITIES. YOU MIGHT WANT TO UPDATE PACKAGES BEFORE USE.**
## Setup
- run `npm i`
- get the .env file and save it to the root directory of the project
- set up Strapi
- go to `localhost:1337/admin`
- register an admin user
- go to Settings => Users&Permissions Plugin => Roles => Authenticated => Note => Select all
- Save
### Starting the container with svelte and strapi:
``docker-compose up --build -d``

View file

@ -50,9 +50,6 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
lastViewed: Date.now()
}
})
entry = await strapi.entityService.findOne(noteUid, noteId, {
populate: ['owners'],
});
return JSON.stringify(entry);
} else {
ctx.response.status = 403;
@ -66,26 +63,22 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
async update(ctx) {
const noteId = getNoteIdFromUrl(ctx.request.url)
const userId = ctx.state.user.id;
const requestBody = JSON.parse(ctx.request.body);
console.log(JSON.stringify(requestBody, null, 2))
const requestBody = ctx.request.body;
const entry = await strapi.entityService.findOne(noteUid, noteId, {
populate: ['owners'],
});
const authorized = entry.owners.some(owner => owner.id === userId)
let allPreviousOwnersKept = true;
let allPreviousOwnersKept = false;
if (requestBody.data.hasOwnProperty("owners")) {
allPreviousOwnersKept = entry.owners.every(owner => requestBody.data.owners.includes(owner));
}
console.log({
"auth": authorized,
"allprev": allPreviousOwnersKept,
})
if (!authorized) {
ctx.response.status = 403;
} else if (!allPreviousOwnersKept) {
ctx.response.status = 400;
} else {
return super.update(ctx);
}
return await strapi.entityService.update(noteUid, noteId, requestBody);
},
/**
* Creates a new note, automatically sets owners to the user making the request and lastViewed
@ -94,8 +87,7 @@ module.exports = createCoreController(noteUid, ({strapi}) => ({
*/
async create(ctx) {
const userId = ctx.state.user.id;
const requestBody = JSON.parse(ctx.request.body);
console.log(requestBody);
const requestBody = ctx.request.body;
const response = await strapi.entityService.create(noteUid, {
data: {
title: requestBody.data.title,

View file

@ -1,73 +0,0 @@
{
"kind": "collectionType",
"collectionName": "up_users",
"info": {
"name": "user",
"description": "",
"singularName": "user",
"pluralName": "users",
"displayName": "User"
},
"options": {
"draftAndPublish": false,
"timestamps": true
},
"attributes": {
"username": {
"type": "string",
"minLength": 3,
"unique": true,
"configurable": false,
"required": true
},
"email": {
"type": "email",
"minLength": 6,
"configurable": false,
"required": true
},
"provider": {
"type": "string",
"configurable": false
},
"password": {
"type": "password",
"minLength": 6,
"configurable": false,
"private": true
},
"resetPasswordToken": {
"type": "string",
"configurable": false,
"private": true
},
"confirmationToken": {
"type": "string",
"configurable": false,
"private": true
},
"confirmed": {
"type": "boolean",
"default": false,
"configurable": false
},
"blocked": {
"type": "boolean",
"default": false,
"configurable": false
},
"role": {
"type": "relation",
"relation": "manyToOne",
"target": "plugin::users-permissions.role",
"inversedBy": "users",
"configurable": false
},
"notes": {
"type": "relation",
"relation": "manyToMany",
"target": "api::note.note",
"inversedBy": "owners"
}
}
}

View file

@ -1,3 +0,0 @@
remote_theme: pages-themes/leap-day@v0.2.0
plugins:
- jekyll-remote-theme

View file

@ -1,4 +0,0 @@
name,github,image
Jonas Weissengruber,j-weissen,jowei
Stefan Prechtler,s-prechtl,stef
David Hain,d-hain,dave
1 name github image
2 Jonas Weissengruber j-weissen jowei
3 Stefan Prechtler s-prechtl stef
4 David Hain d-hain dave

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

View file

@ -1,31 +0,0 @@
# Pomelo Note
This is the best open source note app you will ever find.
## Login
When first entering the app, you will need to login. If you haven't got an account you may consider [registering](#register), or just not using the app at all.
<br /><img src="images/login.png" width="50%" style="margin: auto; display: block;" />
## Register
A username, an email and a password that's all you need. If you are missing one of those, just don't use the app at all.
<br /><img src="images/register.png" width="50%" style="margin: auto; display: block;" />
## Editor
You can edit your notes with our minimalistic editor interface.
<br /><img src="images/editor.png" width="50%" style="margin: auto; display: block;" />
## Listing
Here you can see all your notes. Click on them to open the editor or hover and press the red "X" to delete them.
<br /><img src="images/listing.png" width="50%" style="margin: auto; display: block;" />
## Delete
Confirm the deletion.
<br /><img src="images/delete.png" width="50%" style="margin: auto; display: block;" />
# The Team
{% for dev in site.data.devs %}
{{ dev.name }}
[GitHub](https://github.com/{{ dev.github }})
![{{ dev.name }}](images/{{ dev.image }}.jpg)
{% endfor %}

View file

@ -9,9 +9,7 @@
"version": "0.0.1",
"dependencies": {
"bootstrap-icons": "^1.9.1",
"nookies": "^2.5.2",
"sv-popup": "^0.2.5",
"webworker": "^0.8.4"
"nookies": "^2.5.2"
},
"devDependencies": {
"@sveltejs/adapter-auto": "next",
@ -2085,11 +2083,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sv-popup": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/sv-popup/-/sv-popup-0.2.5.tgz",
"integrity": "sha512-JhBu4piXaauamT4vMEcFCydvxJ8e72G7c9F3caZVAPsiFqWPTYT3JDz99FlR+YCnbOp1emsZqqOPVvCwHgURog=="
},
"node_modules/svelte": {
"version": "3.50.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.50.1.tgz",
@ -2365,14 +2358,6 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"dev": true
},
"node_modules/webworker": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/webworker/-/webworker-0.8.4.tgz",
"integrity": "sha512-zzsVxtHf+mCn0WuYLarSWfRGmX7JiYKkKvso5FYC7rJ9G8svwGQA5a51Sjq9D2c/rKVU6U/kyBcaI7gUTVlsJg==",
"engines": {
"node": ">=0.4.3"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@ -3839,11 +3824,6 @@
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true
},
"sv-popup": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/sv-popup/-/sv-popup-0.2.5.tgz",
"integrity": "sha512-JhBu4piXaauamT4vMEcFCydvxJ8e72G7c9F3caZVAPsiFqWPTYT3JDz99FlR+YCnbOp1emsZqqOPVvCwHgURog=="
},
"svelte": {
"version": "3.50.1",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.50.1.tgz",
@ -4001,11 +3981,6 @@
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"dev": true
},
"webworker": {
"version": "0.8.4",
"resolved": "https://registry.npmjs.org/webworker/-/webworker-0.8.4.tgz",
"integrity": "sha512-zzsVxtHf+mCn0WuYLarSWfRGmX7JiYKkKvso5FYC7rJ9G8svwGQA5a51Sjq9D2c/rKVU6U/kyBcaI7gUTVlsJg=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",

View file

@ -22,9 +22,7 @@
},
"type": "module",
"dependencies": {
"bootstrap-icons": "^1.9.1",
"nookies": "^2.5.2",
"sv-popup": "^0.2.5",
"webworker": "^0.8.4"
"bootstrap-icons": "^1.9.1"
}
}

View file

@ -1,10 +1,9 @@
<!DOCTYPE html>
<html lang="en" data-theme="emerald">
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="../src/resources/images/logo2.svg" />
<meta name="viewport" content="width=device-width" />
<link rel="manifest" href="../static/manifest.json">
%sveltekit.head%
</head>
<body>

View file

@ -1,29 +0,0 @@
html,
:root {
--main-txt-color: black;
--cross-txt-color: red;
--color-primary: #fff494;
--color-primary-600: #fff17a;
--color-primary-700: #ffec47;
--color-primary-800: #ffe714;
--color-primary-900: #e0c900;
}
.btn-primary {
background-color: var(--color-primary-800) !important;
border: var(--color-primary-800) !important;
color: var(--main-txt-color) !important;
}
.btn-primary:hover {
background-color: var(--color-primary-900) !important;
border: var(--color-primary-900) !important;
color: var(--main-txt-color) !important;
}
.btn-primary:disabled {
background-color: var(--color-primary-700) !important;
border: var(--color-primary-700) !important;
}

View file

@ -1,4 +1,4 @@
import type {Note} from "../../types";
import type {Note} from "./types";
export interface NoteRepository {
getNotes(): Promise<Note[]>;

View file

@ -1,5 +1,4 @@
import type {Authentication} from "./authentication";
import {createErrorToast} from "./customToasts";
import {parseCookies} from "nookies";
/**
* Capitalises first letter of string.
@ -23,14 +22,13 @@ export async function bearerFetch(endpoint: string, jwt: string, baseUrl: string
});
}
export function handleErrorsFromResponseWithToast(response: Authentication) {
if (response.error != null) {
if (response.error.details.errors) {
for (const error of response.error.details.errors) {
createErrorToast(error.message);
}
} else {
createErrorToast(response.error.message);
}
}
}
const getJwtCookie = () => {
// @ts-ignore
return parseCookies("/").jwt;
};
/**
* JWT Cookie
*/
export const jwt: string = getJwtCookie();

View file

@ -1,8 +1,6 @@
import type {Note} from "../../types";
import type {Note} from "./types";
import {parseCookies} from "nookies";
import type {NoteRepository} from "./NoteRepository";
import {currentNoteId} from "../../../stores";
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE'
@ -15,21 +13,11 @@ export class StrapiNoteRepository implements NoteRepository {
return this.instance;
}
private constructor() {
currentNoteId.subscribe((value) => (this._currentNoteId = value));
}
private constructor() {}
private _currentNoteId: unknown;
private currentNoteId: number | undefined;
private static apiNoteEndpoint: string = "http://localhost:1337/api/notes"
public set currentNoteId(value: number | undefined) {
currentNoteId.set(value || -1);
}
public get currentNoteId(): number {
return <number>this._currentNoteId;
}
public async getNotes(): Promise<Note[]>{
const response = await StrapiNoteRepository.fetchStrapiNoteEndpoint("/", 'GET');
return await response.json();
@ -41,7 +29,7 @@ export class StrapiNoteRepository implements NoteRepository {
}
public async getCurrentNote(): Promise<Note | void> {
if (this._currentNoteId === null || this._currentNoteId === undefined) {
if (this.currentNoteId === null || this.currentNoteId === undefined) {
return;
}
return await this.getNote(this.currentNoteId);
@ -65,7 +53,7 @@ export class StrapiNoteRepository implements NoteRepository {
let requestInit: RequestInit = {
method: method,
headers: {
authorization: StrapiNoteRepository.getAuthorizationHeader()
authorization: StrapiNoteRepository.mockedGetAuthorizationHeader()
}
};
if (body) {
@ -78,9 +66,8 @@ export class StrapiNoteRepository implements NoteRepository {
return "bearer TOKEN"
}
static getAuthorizationHeader() {
// @ts-ignore
const jwt = parseCookies('/').jwt;
private static getAuthorizationHeader() {
const jwt = parseCookies().jwt;
return `bearer ${jwt}`
}
}

View file

@ -1,93 +0,0 @@
import type {UserRepository} from "./UserRepository";
import type {Authentication} from "../../authentication";
import type {HttpMethod} from "@sveltejs/kit/types/private";
import {StrapiNoteRepository} from "../note/StrapiNoteRepository";
import {error} from "@sveltejs/kit";
import {User} from "../../user";
export class StrapiUserRepo implements UserRepository {
private static instance: StrapiUserRepo;
public static getInstance(verification: boolean = true): StrapiUserRepo {
if (this.instance === undefined || this.instance === null) {
this.instance = new StrapiUserRepo();
this.instance.verify().then(() => {
if (verification && !this.instance.verified) {
window.location.href = "/login";
}
});
}
return this.instance;
}
private verified: boolean = false;
private constructor() {
}
private static api: string = "http://localhost:1337/api"
private static apiUserEndpoint: string = StrapiUserRepo.api + "/auth/local"
/**
* Verifies the current users jwt.
* @private
*/
private async verify() {
this.verified = false;
let result = await this.getMe();
if (!result.error) {
this.verified = true;
}
}
async getMe(): Promise<Authentication> {
const response = await StrapiUserRepo.fetchStrapi("/me", "GET", null, true, "/users")
return await response.json();
}
async registerUser(email: string, username: string, password: string): Promise<Authentication> {
const payload = {
email: email,
password: password,
username: username
};
const response = await StrapiUserRepo.fetchStrapi("/register", "POST", payload, false);
return await response.json();
}
async loginUser(identifier: string, password: string): Promise<Authentication> {
const payload = {
identifier: identifier,
password: password
};
const response = await StrapiUserRepo.fetchStrapi("/", "POST", payload, false);
return response.json();
}
private static async fetchStrapi(path: string, method: HttpMethod, body: any | null = null, authorization: boolean = true, customPath: any = null): Promise<Response> {
let requestInit: RequestInit = {
method: method,
};
if (authorization && body) {
requestInit["headers"] = {
authorization: StrapiNoteRepository.getAuthorizationHeader() ?? '',
'Accept': 'application/json',
'Content-Type': 'application/json'
}
} else if (authorization) {
requestInit["headers"] = {
authorization: StrapiNoteRepository.getAuthorizationHeader() ?? '',
}
} else if (body) {
requestInit["headers"] = {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}
if (body) {
requestInit["body"] = JSON.stringify(body)
}
return await fetch((customPath) ? (this.api + customPath + path) : StrapiUserRepo.apiUserEndpoint + path, requestInit);
}
}

View file

@ -1,19 +0,0 @@
import type {Authentication} from "../../authentication";
export interface UserRepository {
/**
* Registers a new user.
* @param email
* @param username
* @param password
*/
registerUser(email: string, username: string, password: string): Promise<Authentication>;
/**
* Gets the current user.
* @param jwt
*/
getMe(jwt: string): Promise<Authentication>;
loginUser(identifier: string, password: string): Promise<Authentication>;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,48 +1,87 @@
<script lang="ts">
import type {Note} from "../models/types";
import {onMount} from "svelte";
import {StrapiNoteRepository} from "../models/repos/note/StrapiNoteRepository";
import {StrapiUserRepo} from "../models/repos/user/StrapiUserRepo";
import {Content, Modal, Trigger} from "sv-popup";
import {bearerFetch, jwt} from "../models/PomeloUtils";
const endpoint = "/notes";
//
// //:TODO TEMP!!!
// const jsonStr = "[{\"id\":0,\"attributes\":{\"title\":\"mike\",\"content\":\"C Moasta\",\"lastViewed\":\"2022-09-27\"}},{\"id\":1,\"attributes\":{\"title\":\"samc\",\"content\":\"drupal gott\",\"lastViewed\":\"1999-09-09\"}},{\"id\":2,\"attributes\":{\"title\":\"DIO\",\"content\":\"in all CAPS\",\"lastViewed\":\"2022-09-27\"}},{\"id\":3,\"attributes\":{\"title\":\"Eren\",\"content\":\"Jäger\",\"lastViewed\":\"2022-09-27\"}},{\"id\":4,\"attributes\":{\"title\":\"stow\",\"content\":\"Beitn Chef\",\"lastViewed\":\"2022-09-27\"}},{\"id\":5,\"attributes\":{\"title\":\"Wonder of U\",\"content\":\"Umm... so, personally... this is the first time this has happened, so I'm a bit surprised. Only a centimeter away... I mean, I don't think there's ever been someone who's gotten that close to me... without a, you know... calamity occurring. I'm not really... not really sure what happens at one centimeter away... 'cause it's my first time. I don't really understand it either. Seriously. But in the flow of calamity... there's nobody who can attack me. Not a single person. That, I know for sure. Wonder of U.\",\"lastViewed\":\"2022-09-27\"}}]";
// //:TODO TEMP!!!
//
// let notes: Note[] = JSON.parse(jsonStr);
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const noteRepo: StrapiNoteRepository = StrapiNoteRepository.getInstance();
let notes: Note[];
onMount(async () => {
StrapiUserRepo.getInstance();
notes = await noteRepo.getNotes();
const response = await bearerFetch(endpoint, jwt);
let data = await response.json();
notes = data.data;
notes.forEach(note => {
note.lastViewed = new Date(note.lastViewed);
note.attributes.lastViewed = new Date(note.attributes.lastViewed);
});
console.log(notes);
});
/**
* Adds a Note with the title "New Note" and redirects to editor
* Reloads the Notes Listing
* (by doing something very intelligent)
*/
async function addNoteHandler() {
const newTitle = "New Note";
const newNote = await addNote(newTitle);
noteRepo.currentNoteId = newNote.id;
window.location = "/editor";
function reloadNotesListing() {
notes = notes.filter(i => i === i);
}
/**
* Adds a new note to the Database
* Gives the user a prompt to input the new title of the note and creates it if the title is valid
*/
function addNotePrompt() {
let newTitle = prompt('Name of the new Note');
console.log(notes)
if (newTitle != null && newTitle != '') {
addNote(newTitle);
console.log(notes)
reloadNotesListing();
}
}
/**
* Adds a new note to the "notes" Array with:
* * the latest id + 1 (or 0 if no notes exist)
* * no content
* * the current date as the "lastViewed" property
* @param title The title of the new Note
* @return The created Note Object
*/
async function addNote(title: string): Promise<Note> {
return await noteRepo.createNote({title: title,});
function addNote(title: string) {
const date = new Date();
const newNoteId: number = (notes.length == 0) ? 0 : notes[notes.length - 1].id + 1
const note: Note = {
id: newNoteId,
attributes: {
title: title,
content: "",
lastViewed: date
}
};
notes.push(note);
}
/**
* Deletes the note from the "notes" Array and the database
* Gives the user a prompt if they are sure to delete this note and deletes it if they confirm
* @param note The note to be deleted
*/
function deleteNotePrompt(note) {
const reallyDelete = confirm("Do you really want to delete this Note?");
if (reallyDelete) {
deleteNote(note);
}
}
/**
* Deletes the note from the "notes" Array
* @param note The note to be deleted
*/
function deleteNote(note) {
notes = notes.filter(i => i !== note);
noteRepo.deleteNote(note.id);
}
/**
@ -62,44 +101,32 @@
}
/**
* Sets the currentNoteId and redirects to the editor
* Handles a click on a note list element
* @param note The note the user clicked on
*/
function onNoteLiClick(note) {
noteRepo.currentNoteId = note.id;
window.location = "/editor";
note.attributes.lastViewed = new Date();
}
/**
* Closes the modal (popup for deletion)
*/
async function closeModal() {
closeModalBool = true;
await sleep(1);
closeModalBool = false;
}
let closeModalBool = false;
</script>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PomeloNote | Home</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css" rel="stylesheet">
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
</head>
<body>
<div class="container">
<div class="row">
<!-- Add Note Button -->
<div class="offset-md-7 col-md-1">
<button class="btn btn-primary" on:click={() => addNoteHandler()}>Add Note</button>
<button class="btn btn-primary" on:click={() => addNotePrompt()}>Add Note</button>
</div>
</div>
@ -114,42 +141,18 @@
<div class="row">
<div class="col-10" on:click={() => onNoteLiClick(note)}>
<div>
{note.title}
{note.attributes.title}
</div>
<div class="list-date-text">
{note.lastViewed.toLocaleDateString()}
{note.attributes.lastViewed.toLocaleDateString()}
</div>
</div>
<div class="col-1">
<!-- Delete Note Popup -->
<Modal basic small={true} button={false} close={closeModalBool}>
<Content>
<div class="row" style="margin-bottom: 10px;">
<h5>Do you really want to delete the "{note.title}" note?</h5>
</div>
<div class="row">
<div class="col-4 offset-2">
<button class="btn btn-primary"
on:click={() => deleteNote(note)}
on:click={() => closeModal()}>
<b>Yes</b>
</button>
</div>
<div class="col-4 offset-2">
<button class="btn btn-primary" autofocus
on:click={() => closeModal()}>
<b>No</b>
</button>
</div>
</div>
</Content>
<Trigger>
<button style="display: none" id={"noteButton" + note.id}>
<button style="display: none" id={"noteButton" + note.id}
on:click={() => deleteNotePrompt(note)}>
<i class="bi bi-x"></i>
</button>
</Trigger>
</Modal>
</div>
</div>
</li>
@ -163,11 +166,17 @@
</html>
<style>
@import "../customBootstrap.css";
html,
:root {
--main-txt-color: black;
--sub-txt-color: gray;
--cross-txt-color: red;
--color-primary: #fff494;
--color-primary-600: #fff17a;
--color-primary-700: #ffec47;
--color-primary-800: #ffe714;
--color-primary-900: #e0c900;
}
body {
@ -221,6 +230,18 @@
color: var(--cross-txt-color);
}
.btn-primary {
background-color: var(--color-primary-800);
border: var(--color-primary-800);
color: var(--main-txt-color);
}
.btn-primary:hover {
background-color: var(--color-primary-900);
border: var(--color-primary-900);
color: var(--main-txt-color);
}
.list-date-text {
color: var(--sub-txt-color);
font-size: 0.8314159265358979323846264338rem;

View file

@ -1,6 +1,20 @@
import {StrapiUserRepo} from "../models/repos/user/StrapiUserRepo";
import {bearerFetch, jwt} from "../models/PomeloUtils";
/** @type {import('./$types').PageLoad} */
export async function load() {
// StrapiUserRepo.getInstance();
let invalid = !jwt;
if (!invalid) {
const request = await bearerFetch("/users/me", jwt);
const response = await request.json();
invalid = "error" in response;
}
if (invalid) {
if (typeof window !== 'undefined') {
// @ts-ignore
window.location = "/login";
}
}
}

View file

@ -1,81 +1,24 @@
<script lang="ts">
import type {Note} from "../../models/types";
import {StrapiNoteRepository} from "../../models/repos/note/StrapiNoteRepository";
import {onMount} from "svelte";
let noteRepo: StrapiNoteRepository;
let currentNote: Note;
onMount(async () => {
noteRepo = StrapiNoteRepository.getInstance();
try {
currentNote = await noteRepo.getNote(noteRepo.currentNoteId);
} catch {
returnToListing();
}
title = (<Note>currentNote).title;
content = (<Note>currentNote).content;
})
/**
* saves the currently opened Note and returns to listing
*/
function saveAndQuit() {
noteRepo.updateNote(currentNote.id, {
"title": title,
"content": content,
})
returnToListing();
}
/**
* redirects to listing
*/
function returnToListing() {
window.location = "/";
}
export let title: string, content: string;
let notes: Note[] = JSON.parse(window.localStorage.getItem("notes"));
const clickedNoteId = window.localStorage.getItem("clickedNoteId");
const currNote = notes.find((note)=>{
return note.id === parseInt(clickedNoteId);
});
</script>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Editor</title>
<title>{"Pomelonote | Edit " + currNote.title}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
</head>
<html lang="en">
<div class="offset-3 col-6 wrapper">
<h1 class="">{title === "" ? "" : title}</h1>
<input bind:value={title} class="input"> <br/>
<textarea bind:value={content} class="input textarea"></textarea>
<div class="button-container">
<button on:click={() => saveAndQuit()} class="btn btn-primary">Save</button>
<button on:click={() => returnToListing()} class="btn btn-outline-danger">Cancel</button>
</div>
<div class="offset-3 col-6">
{currNote.content}
</div>
</html>
<style>
@import "../../customBootstrap.css";
.wrapper {
margin-top: 20px;
}
.input {
margin-bottom: 10px;
width: 100%;
}
.button-container {
float: right;
}
.textarea {
height: 300px;
}
</style>

View file

@ -1,9 +1,9 @@
<script lang="ts">
import {setCookie} from "nookies";
import type {Authentication} from "./models/authentication";
import { SvelteToast } from '@zerodevx/svelte-toast'
import {createErrorToast} from "../../models/customToasts";
import logo from "../../resources/images/logo2.svg";
import {handleErrorsFromResponseWithToast} from "../../models/PomeloUtils";
import {StrapiUserRepo} from "../../models/repos/user/StrapiUserRepo";
let user: string;
let password: string;
@ -14,12 +14,31 @@
* Handles the button click.
*/
async function handleSubmit() {
const userRepo: StrapiUserRepo = StrapiUserRepo.getInstance(false);
const endpoint = "http://localhost:1337/api/auth/local";
const payload = {
identifier: user,
password: password
};
const response = await userRepo.loginUser(user, password);
const login = await fetch(endpoint, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
const response: Authentication = await login.json();
if (response.error != null){
handleErrorsFromResponseWithToast(response);
if (response.error.details.errors){
for (const error of response.error.details.errors) {
createErrorToast(error.message);
}
} else{
createErrorToast(response.error.message);
}
} else {
if (rememberMe) {
setCookie(null, 'jwt', response.jwt, {
@ -37,34 +56,34 @@
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PomeloNote | Login</title>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
</head>
<body>
<main class="form-signin w-100 m-auto">
<img alt="Logo" class="img-fluid" src="{logo}">
<img class="img-fluid" src="{logo}" alt="Logo">
<h1 class="h3 mb-3 fw-normal">Please sign in</h1>
<div class="form-floating">
<input bind:value={user} class="form-control" id="floatingInput" placeholder="name@example.com" type="text">
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com" bind:value={user}>
<label for="floatingInput">Email address or username</label>
</div>
<div class="form-floating">
<input bind:value={password} class="form-control" id="floatingPassword" placeholder="Password" type="password">
<input type="password" class="form-control" id="floatingPassword" placeholder="Password" bind:value={password}>
<label for="floatingPassword">Password</label>
</div>
<div class="checkbox mb-3">
<label>
<input bind:checked={rememberMe} type="checkbox" value="rememberMe"> Remember me
<input type="checkbox" value="rememberMe" bind:checked={rememberMe}> Remember me
</label>
</div>
<button class="w-100 btn btn-lg btn-primary" on:click={handleSubmit}>Sign in</button>
<a class="opacity-75 d-flex justify-content-center text-center fs-6" href="/register">No user yet? Register.</a>
<a href="/register" class="opacity-75 d-flex justify-content-center text-center fs-6">No user yet? Register.</a>
<p class="mt-5 mb-3 text-muted">&copy;2022</p>
</main>
@ -72,7 +91,42 @@
</body>
</html>
<style>
@import "../../userInput.css";
@import "../../customBootstrap.css";
html,
body {
height: 100%;
}
body {
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
max-width: 330px;
padding: 15px;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.img-fluid{
margin-bottom: 15px;
}
</style>

View file

@ -1,4 +1,4 @@
import type {User} from "./user";
import type {User} from "../../../models/user";
/**
* User Login Auth.

View file

@ -1,8 +1,8 @@
<script lang="ts">
import logo from "../../resources/images/logo2.svg";
import {SvelteToast} from "@zerodevx/svelte-toast";
import {StrapiUserRepo} from "../../models/repos/user/StrapiUserRepo";
import {handleErrorsFromResponseWithToast} from "../../models/PomeloUtils";
import type {Authentication} from "../login/models/authentication";
import {createErrorToast} from "../../models/customToasts";
let user: string;
let password: string;
@ -13,12 +13,32 @@
* Handles the button click.
*/
async function handleSubmit() {
const userRepo: StrapiUserRepo = StrapiUserRepo.getInstance(false);
const endpoint = "http://localhost:1337/api/auth/local/register";
const payload = {
email: email,
password: password,
username: user
};
const response = await userRepo.registerUser(email, user, password);
const login = await fetch(endpoint, {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
const response: Authentication = await login.json();
if (response.error != null) {
handleErrorsFromResponseWithToast(response);
if (response.error.details.errors) {
for (const error of response.error.details.errors) {
createErrorToast(error.message);
}
} else {
createErrorToast(response.error.message);
}
} else {
window.location = "/login";
}
@ -29,29 +49,29 @@
lang="en">
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PomeloNote | Register</title>
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css"
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-iYQeCzEYFbKjA/T2uDLTpkwGzCiq6soy8tYaI1GyVh/UjpbCx/TYkiZhlZB6+fzT" crossorigin="anonymous">
</head>
<body>
<main class="form-signin w-100 m-auto">
<img alt="Logo" class="img-fluid" src="{logo}">
<img class="img-fluid" src="{logo}" alt="Logo">
<h1 class="h3 mb-3 fw-normal">Register a new user</h1>
<div class="form-floating">
<input bind:value={user} class="form-control" id="floatingUsr" placeholder="exampleUsername" type="text">
<input type="text" class="form-control" id="floatingUsr" placeholder="exampleUsername" bind:value={user}>
<label for="floatingUsr">Username</label>
</div>
<div class="form-floating">
<input bind:value={email} class="form-control" id="floatingInput" placeholder="name@example.com" type="email">
<input type="email" class="form-control" id="floatingInput" placeholder="name@example.com" bind:value={email}>
<label for="floatingInput">Email address</label>
</div>
<div class="form-floating">
<input bind:value={password} class="form-control" id="floatingPassword" placeholder="Password" type="password">
<input type="password" class="form-control" id="floatingPassword" placeholder="Password" bind:value={password}>
<label for="floatingPassword">Password</label>
</div>
@ -59,7 +79,7 @@
Register user
{#if user}: {user} {/if}
</button>
<a class="opacity-75 d-flex justify-content-center text-center fs-6" href="/login">Already registered? Login.</a>
<a href="/login" class="opacity-75 d-flex justify-content-center text-center fs-6">Already registered? Login.</a>
<p class="mt-5 mb-3 text-muted">&copy;2022</p>
</main>
@ -68,6 +88,46 @@
</html>
<style>
@import "../../userInput.css";
@import "../../customBootstrap.css";
html,
body {
height: 100%;
}
body {
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
max-width: 330px;
padding: 15px;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.img-fluid {
margin-bottom: 15px;
}
</style>

View file

@ -1,78 +0,0 @@
/// <reference lib="webworker" />
import { build, files, version } from '$service-worker';
const worker = ServiceWorkerGlobalScope;
// const FILES = cache + version;
const to_cache = build.concat(files);
const staticAssets = new Set(to_cache);
worker.addEventListener('install', (event) => {
event.waitUntil(
caches
.open(FILES)
.then((cache) => cache.addAll(to_cache))
.then(() => {
worker.skipWaiting();
})
);
});
worker.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then(async (keys) => {
// delete old caches
for (const key of keys) {
if (key !== FILES) await caches.delete(key);
}
worker.clients.claim();
})
);
});
/**
* Fetch the asset from the network and store it in the cache.
* Fall back to the cache if the user is offline.
*/
async function fetchAndCache(request) {
const cache = await caches.open(offline + version);
try {
const response = await fetch(request);
cache.put(request, response.clone());
return response;
} catch (err) {
const response = await cache.match(request);
if (response) return response;
throw err;
}
}
worker.addEventListener('fetch', (event) => {
if (event.request.method !== 'GET' || event.request.headers.has('range')) return;
const url = new URL(event.request.url);
// don't try to handle e.g. data: URIs
const isHttp = url.protocol.startsWith('http');
const isDevServerRequest =
url.hostname === self.location.hostname && url.port !== self.location.port;
const isStaticAsset = url.host === self.location.host && staticAssets.has(url.pathname);
const skipBecauseUncached = event.request.cache === 'only-if-cached' && !isStaticAsset;
if (isHttp && !isDevServerRequest && !skipBecauseUncached) {
event.respondWith(
(async () => {
// always serve static files and bundler-generated assets from cache.
// if your application has other URLs with data that will never change,
// set this variable to true for them and they will only be fetched once.
const cachedAsset = isStaticAsset && (await caches.match(event.request));
return cachedAsset || fetchAndCache(event.request);
})()
);
}
});

View file

@ -1,7 +0,0 @@
import {writable} from "svelte/store";
import {browser} from "$app/environment"
export const currentNoteId = writable<number>();
if (browser) {
currentNoteId.set(Number(localStorage.getItem("currentNoteId") || ""))
currentNoteId.subscribe(val => localStorage.setItem("currentNoteId", String(val)));
}

View file

@ -1,41 +0,0 @@
html,
body {
height: 100%;
}
body {
align-items: center;
padding-top: 40px;
padding-bottom: 40px;
background-color: #f5f5f5;
}
.form-signin {
max-width: 330px;
padding: 15px;
}
.form-signin .form-floating:focus-within {
z-index: 2;
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
}
.form-signin input[type="email"] {
margin-bottom: -1px;
border-radius: 0;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.img-fluid {
margin-bottom: 15px;
}

View file

@ -1,41 +0,0 @@
{
"lang": "en",
"dir": "/",
"name": "Pomelo Note",
"short_name": "Pomelo",
"description": "Best Note App",
"theme_color": "#000",
"background_color": "#000",
"display": "standalone",
"orientation": "portrait",
"prefer_related_applications": false,
"scope": "/",
"start_url": "/",
"icons": [
{
"src": "../resources/icons/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "../resources/icons/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "../resources/icons/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "../resources/icons/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
}
],
"splash_pages": null
}

View file

@ -2,14 +2,7 @@ import { sveltekit } from '@sveltejs/kit/vite';
import type { UserConfig } from 'vite';
const config: UserConfig = {
plugins: [sveltekit()],
server: {
fs: {
// Allow serving files from one level up to the project root
allow: ['..'],
},
},
plugins: [sveltekit()]
};
export default config;