| @ -0,0 +1,124 @@ | |||||
| import validate from 'validate.js' | |||||
| import serialize from 'form-serialize' | |||||
| /** | |||||
| * Form Validator with RiotJS Components | |||||
| * | |||||
| * | |||||
| * | |||||
| * | |||||
| */ | |||||
| class FormValidator | |||||
| { | |||||
| /** | |||||
| * | |||||
| * @param {[type]} formSelector [description] | |||||
| * @param {[type]} constraits [description] | |||||
| */ | |||||
| constructor(formSelector, constraits, onSuccess) | |||||
| { | |||||
| // getting selector to find form-element | |||||
| this.formSelector = formSelector | |||||
| // constraits for validate.js | |||||
| this.constraits = constraits | |||||
| // get form and elements | |||||
| this.form = document.querySelector(this.formSelector) | |||||
| this.elements = this.form.querySelectorAll('field-error') | |||||
| // adding submit event | |||||
| this.form.addEventListener('submit', (event) => { | |||||
| this.onSubmit(event) | |||||
| }) | |||||
| // adding event if a element is updated | |||||
| this.form.addEventListener('field-update', (event) => { | |||||
| this.onFieldUpdate(event) | |||||
| }) | |||||
| this.onSuccess = onSuccess | |||||
| } | |||||
| /** | |||||
| * | |||||
| * @param {[type]} event [description] | |||||
| * @return {[type]} [description] | |||||
| */ | |||||
| onSubmit(event) | |||||
| { | |||||
| event.preventDefault() | |||||
| let errors = validate(serialize(event.target, { | |||||
| hash: true | |||||
| }), this.constraits, { | |||||
| fullMessages: false | |||||
| }) | |||||
| if (errors) { | |||||
| // send each element a event | |||||
| this.elements.forEach((element) => { | |||||
| let elementErrors = false | |||||
| // check for errors by name | |||||
| if (errors[element.attributes.name.nodeValue]) { | |||||
| elementErrors = errors[element.attributes.name.nodeValue] | |||||
| } | |||||
| this.dispatchCustomEvent(elementErrors, element) | |||||
| }) | |||||
| } else { | |||||
| this.onSuccess(event, serialize(event.target, { | |||||
| hash: true | |||||
| })) | |||||
| } | |||||
| } | |||||
| /** | |||||
| * | |||||
| * | |||||
| * @param {Event} event | |||||
| * | |||||
| */ | |||||
| onFieldUpdate(event) | |||||
| { | |||||
| // workaround, make sure that value for single is undefined if it is empty | |||||
| if (event.detail.value == '') { | |||||
| event.detail.value = undefined | |||||
| } | |||||
| let errors = validate.single(event.detail.value, this.constraits[event.detail.name]) | |||||
| // search for element by name and dispatch event | |||||
| this.elements.forEach((element) => { | |||||
| if (element.attributes.name.nodeValue == event.detail.name) { | |||||
| this.dispatchCustomEvent(errors, element) | |||||
| } | |||||
| }) | |||||
| } | |||||
| /** | |||||
| * dispatch event to single element | |||||
| * | |||||
| * @param {Array} errors | |||||
| * @param {Element} element | |||||
| * | |||||
| */ | |||||
| dispatchCustomEvent(errors, element) | |||||
| { | |||||
| let detail = false | |||||
| if (errors) { | |||||
| detail = errors | |||||
| } | |||||
| const formValidationEvent = new CustomEvent('form-validation', { | |||||
| 'detail': detail | |||||
| }) | |||||
| element.dispatchEvent(formValidationEvent) | |||||
| } | |||||
| } | |||||
| export default FormValidator | |||||
| @ -0,0 +1,6 @@ | |||||
| import * as riot from 'riot' | |||||
| import NoteForm from './components/note-form.riot' | |||||
| riot.register('note-form', NoteForm) | |||||
| riot.mount('note-form') | |||||
| @ -0,0 +1,123 @@ | |||||
| <app-note-form> | |||||
| <div class="note-form"> | |||||
| <div class="panel"> | |||||
| <div class="panel__body"> | |||||
| <form id="form" novalidate> | |||||
| <input type="hidden" if={ state.note && state.note._id } name="_id" value="{ state.note._id }" /> | |||||
| <div class="field-group"> | |||||
| <label class="field-label"> | |||||
| title | |||||
| <input type="text" class="field-text" name="title" /> | |||||
| </label> | |||||
| </div> | |||||
| <div class="field-group"> | |||||
| <label class="field-label"> | |||||
| content | |||||
| <textarea name="content" class="field-text"></textarea> | |||||
| </label> | |||||
| </div> | |||||
| <div class=""> | |||||
| <div class="tabs"> | |||||
| </div> | |||||
| </div> | |||||
| <div> | |||||
| <button class="button" if={ !state.note || (state.note && !state.note._id) }> | |||||
| Create | |||||
| </button> | |||||
| <button class="button" type="submit" if={ state.note && state.note._id }> | |||||
| Save | |||||
| </button> | |||||
| </div> | |||||
| </form> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <script> | |||||
| import axios from 'axios' | |||||
| import * as riot from 'riot' | |||||
| import FormValidator from './../FormValidator' | |||||
| import FieldError from './field-error.riot' | |||||
| riot.register('field-error', FieldError) | |||||
| riot.mount('field-error') | |||||
| /** | |||||
| * | |||||
| * | |||||
| * | |||||
| * @author Björn Hase | |||||
| * | |||||
| */ | |||||
| export default { | |||||
| state: { | |||||
| note: undefined | |||||
| }, | |||||
| onBeforeMount() { | |||||
| // show error if props is missing | |||||
| if (!this.props.bucketId) { | |||||
| console.error('ID of Bucket is Missing!') | |||||
| } | |||||
| }, | |||||
| /** | |||||
| * | |||||
| * | |||||
| */ | |||||
| onMounted(props, state) { | |||||
| // create form validation | |||||
| const formValidation = new FormValidator('form', { | |||||
| 'title': { | |||||
| 'length': { | |||||
| 'maximum': 255 | |||||
| } | |||||
| }, | |||||
| 'content': { | |||||
| 'length': { | |||||
| 'maximum': 10922 | |||||
| } | |||||
| } | |||||
| }, (event, data) => { | |||||
| this.handleSubmit(event, data) | |||||
| }) | |||||
| }, | |||||
| /** | |||||
| * | |||||
| * | |||||
| */ | |||||
| handleSubmit(event, data) { | |||||
| let method = 'post' | |||||
| if (this.state.note && this.state.note._id) { | |||||
| method = 'put' | |||||
| } | |||||
| axios({ | |||||
| method: method, | |||||
| url: '/api/note/' + this.props.bucketId, | |||||
| data: data | |||||
| }).then((response) => { | |||||
| this.state.note = response.data.data | |||||
| this.update() | |||||
| }) | |||||
| } | |||||
| } | |||||
| </script> | |||||
| </app-note-form> | |||||
| @ -0,0 +1,49 @@ | |||||
| <app-note> | |||||
| <div class="note"> | |||||
| <div class="panel"> | |||||
| <div class="bar"> | |||||
| <div class="bar__end w-100"> | |||||
| <button class="button button--transparent" onclick={ (event) => { handleEdit(event, note) } }> | |||||
| <svg class="icon fill-text-contrast" aria-hidden="true"> | |||||
| <use xlink:href="/symbol-defs.svg#icon-edit"></use> | |||||
| </svg> | |||||
| </button> | |||||
| <button class="button button--transparent" onclick={ (event) => { handleDelete(event, note) } }> | |||||
| <svg class="icon fill-text-contrast" aria-hidden="true"> | |||||
| <use xlink:href="/symbol-defs.svg#icon-delete"></use> | |||||
| </svg> | |||||
| </button> | |||||
| </div> | |||||
| </div> | |||||
| <div class="note__title"> | |||||
| <h3> | |||||
| { state.note.title } | |||||
| </h3> | |||||
| </div> | |||||
| <div class="panel__body"> | |||||
| { state.note.content } | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <script> | |||||
| import axios from 'axios' | |||||
| import remove from 'lodash.remove' | |||||
| /** | |||||
| * | |||||
| * | |||||
| * | |||||
| * @author Björn Hase | |||||
| * | |||||
| */ | |||||
| export default { | |||||
| state: { | |||||
| note: undefined | |||||
| } | |||||
| } | |||||
| </script> | |||||
| </app-note> | |||||
| @ -0,0 +1,36 @@ | |||||
| .note-form { | |||||
| position: fixed; | |||||
| top: 0; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| .panel { | |||||
| position: relative; | |||||
| z-index: 20; | |||||
| max-width: 33%; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| border-left: 0; | |||||
| border-top: 0; | |||||
| border-bottom: 0; | |||||
| } | |||||
| &:before { | |||||
| position: fixed; | |||||
| top: 0; | |||||
| left: 0; | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| background-color: transparent; | |||||
| transition: background-color .5s; | |||||
| z-index: 19; | |||||
| content: ""; | |||||
| background: rgba(0,0,0,.87); | |||||
| z-index: 0; | |||||
| } | |||||
| } | |||||
| @ -0,0 +1,11 @@ | |||||
| <% layout('./layout.html') %> | |||||
| <div class="container container--app"> | |||||
| <div class="grid"> | |||||
| <div class="col-12"> | |||||
| <h1 class="highlight"> | |||||
| Settings | |||||
| </h1> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| @ -0,0 +1,17 @@ | |||||
| import { Router } from 'https://deno.land/x/opine@1.5.3/mod.ts' | |||||
| const router = Router() | |||||
| /** | |||||
| * render template for settings | |||||
| * | |||||
| * @param request | |||||
| * @param response | |||||
| * @return | |||||
| */ | |||||
| router.get('/', function(request, response, next) | |||||
| { | |||||
| response.render('settings') | |||||
| }) | |||||
| export default router | |||||
| @ -1,43 +0,0 @@ | |||||
| import { Drash } from 'https://deno.land/x/drash@v1.4.4/mod.ts' | |||||
| import { v4 } from 'https://deno.land/std@0.99.0/uuid/mod.ts' | |||||
| import { Bucket as BucketSchema } from './src/schemas/bucket.ts' | |||||
| import Schema, { Type, string, number, array } from 'https://denoporter.sirjosh.workers.dev/v1/deno.land/x/computed_types/src/index.ts' | |||||
| /** | |||||
| * | |||||
| * | |||||
| */ | |||||
| export class BucketResource extends Drash.Http.Resource | |||||
| { | |||||
| // route | |||||
| static paths = ['/bucket/[:id?]'] | |||||
| /** | |||||
| * [GET description] | |||||
| * @param id [description] | |||||
| * @return [description] | |||||
| */ | |||||
| public GET(id) | |||||
| { | |||||
| const db = new Database<BucketSchema>() | |||||
| const buckets = await db.findOne({ | |||||
| '_id': id | |||||
| }) | |||||
| this.response.body = bucket | |||||
| return this.response | |||||
| } | |||||
| /** | |||||
| * | |||||
| * | |||||
| */ | |||||
| public POST() | |||||
| { | |||||
| const db = new Database<BucketSchema>() | |||||
| this.response.body = bucket | |||||
| return this.response | |||||
| } | |||||
| } | |||||
| @ -1,24 +0,0 @@ | |||||
| import { Drash } from 'https://deno.land/x/drash@v1.4.4/mod.ts' | |||||
| import { v4 } from 'https://deno.land/std@0.99.0/uuid/mod.ts' | |||||
| import { Bucket as BucketSchema } from './src/schemas/bucket.ts' | |||||
| /** | |||||
| * | |||||
| * | |||||
| */ | |||||
| export class BucketResource extends Drash.Http.Resource | |||||
| { | |||||
| // route | |||||
| static paths = ['/buckets'] | |||||
| // | |||||
| public GET() | |||||
| { | |||||
| const db = new Database<BucketSchema>() | |||||
| const buckets = await db.findMany() | |||||
| this.response.body = buckets | |||||
| return this.response | |||||
| } | |||||
| } | |||||
| @ -1,18 +0,0 @@ | |||||
| import { Drash } from 'https://deno.land/x/drash@v1.4.4/mod.ts' | |||||
| /** | |||||
| * | |||||
| * | |||||
| */ | |||||
| export class IndexResource extends Drash.Http.Resource | |||||
| { | |||||
| // route | |||||
| static paths = ['/'] | |||||
| // | |||||
| public GET() | |||||
| { | |||||
| this.response.body = 'Hallo' | |||||
| return this.response | |||||
| } | |||||
| } | |||||
| @ -1,8 +1,9 @@ | |||||
| export interface Note { | |||||
| export interface NoteSchema { | |||||
| _id: string; | _id: string; | ||||
| title: string; | title: string; | ||||
| type: string; | type: string; | ||||
| content: string; | content: string; | ||||
| attachment: array; | |||||
| attachment: any[]; | |||||
| authors: string[]; | |||||
| tags: string[]; | tags: string[]; | ||||
| } | } | ||||