| @ -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; | |||
| title: string; | |||
| type: string; | |||
| content: string; | |||
| attachment: array; | |||
| attachment: any[]; | |||
| authors: string[]; | |||
| tags: string[]; | |||
| } | |||