GithubHelp home page GithubHelp logo

boostio / boostnote-legacy Goto Github PK

View Code? Open in Web Editor NEW
17.1K 17.1K 1.5K 18.8 MB

This repository is outdated and new Boost Note app is available! We've launched a new Boost Note app which supports real-time collaborative writing. https://github.com/BoostIO/BoostNote-App

License: Other

JavaScript 82.56% HTML 1.23% Stylus 16.21%
electron linux macos open-source react stylus windows

boostnote-legacy's Introduction

TachiJS

Build Status codecov NPM download Supported by BoostIO

Tachi(太刀) https://en.wikipedia.org/wiki/Tachi

Highly testable dead simple web server written in Typescript

  • 🏁 Highly testable. (all props in req and res are injectable so you don't have to mock at all.)
  • 🔧 Highly customizable.
  • 💉 Simple dependency injection.
  • async/await request handler. (like Koa without any configurations.)
  • 🏭 Based on expressjs. (You can benefit from using this mature library)
  • Built-in request body validator.
  • 📐 Written in Typescript.

Why?

Nest.js looks nice. But its learning curve is too stiff.(TBH, I still don't know how to redirect dynamically.) Most of people probably do not need to know how Interceptor, Pipe and other things work. It might be good for some enterprize level projects.

But using raw expressjs is also quite painful. To test express apps, you have to use supertest or chai-http things. If you use them, you will lose debugging and error stack while testing because they send actual http request internally. Otherwise, you have to mock up all params, req, res and next, of RequestHandler of express.js.

To deal with the testing problem, inversify-express-utils could be a solution. But it does not support many decorators. To render with view engine like pug, we need to use res.render method. But the only solution is using @response decorator. It means you have to mock up Response in your test. So technically it is super hard to test routes rendering view engine.

Luckily, TachiJS tackles those problems. If you have other ideas, please create an issue!!

How to use

Install tachijs

npm i tachijs reflect-metadata

Add two compiler options, experimentalDecorators and emitDecoratorMetadata, to tsconfig.json.

{
  "compilerOptions": {
    ...
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    ...
  }
}

Quick start

import tachijs, { controller, httpGet } from 'tachijs'

@controller('/')
class HomeController() {
  // Define when this method should be used.
  @httpGet('/')
  index() {
    return {
      message: 'Hello, world!'
    }
  }
}

// Register `HomeController`
const app = tachijs({
  controllers: [HomeController]
})

// `app` is just an express application instance
app.listen(8000)

Now you can access http://localhost:8000/.

For other http methods, tachijs provides @httpPost, @httpPut, @httpPatch, @httpDelete, @httpOptions, @httpHead and @httpAll.

Configuring express app(Middlewares)

There are lots of ways to implement express middlewares.

Use before and after options

import bodyParser from 'body-parser'
import { ConfigSetter, NotFoundException } from 'tachijs'

const before: ConfigSetter = app => {
  app.use(bodyParser())
}

const after: ConfigSetter = app => {
  app.use('*', (req, res, next) => {
    next(new NotFoundException('Page does not exist.'))
  })

  const errorHandler: ErrorRequestHandler = (error, req, res, next) => {
    const { status = 500, message } = error
    res.status(status).json({
      status,
      message
    })
  }
  app.use(errorHandler)
}

const app = tachijs({
  before,
  after
})

app.listen(8000)

Without before or after options

Identically same to the above example.

import express from 'express'
import bodyParser from 'body-parser'
import { ConfigSetter, NotFoundException } from 'tachijs'

const app = express()
app.use(bodyParser())

tachijs({
  app
})

app.use('*', (req, res, next) => {
  next(new NotFoundException('Page does not exist.'))
})

const errorHandler: ErrorRequestHandler = (error, req, res, next) => {
  const { status = 500, message } = error
  res.status(status).json({
    status,
    message
  })
}
app.use(errorHandler)

app.listen(8000)

Apply middlewares to controllers and methods

Sometimes, you might want to apply middlewares to several methods only.

import { controller, httpGet, ForbiddenException } from 'tachijs'
import cors from 'cors'
import { RequestHandler } from 'express'

const onlyAdmin: RequestHandler = (req, res, next) => {
  if (!req.user.admin) {
    next(new ForbiddenException('Only admin users can access this api'))
    return
  }
  next()
}

// Apply `cors()` to controller. Now all methods will use the middleware.
@controller('/', [cors()])
class HomeController() {
  @httpGet('/')
  index() {
    return {
      message: 'Hello, world!'
    }
  }

  // Apply `onlyAdmin` to `admin` method. This middleware will be applied to this method only.
  @httpGet('/', [onlyAdmin])
  admin() {
    return {
      message: 'Hello, world!'
    }
  }
}

Configure router options

Tachijs will create and register a router for each controller.

So you can provide router options via @controller decorator.

@controller('/:name', [], {
  // Provide mergeParams option to express router.
  mergeParams: true
})
class HomeController {
  @httpGet('/hello')
  // Now routes in the controller can access params.
  index(@reqParams('name') name: string) {
    return `Hello, ${name}`
  }
}

Access req.params, req.query and req.body via decorators

You can access them via @reqParams, @reqQuery and @reqBody. (Don't forget to apply body-parser middleware)

import {
  controller,
  httpGet,
  httpPost,
  reqParams,
  reqQuery,
  reqBody
} from 'tachijs'

@controller('/posts')
class PostController() {
  @httpGet('/:postId')
  // `req.params.postId`
  async show(@reqParams('postId') postId: string) {
    const post = await Post.findById(postId)

    return {
      post
    }
  }

  @httpGet('/search')
  // `req.query.title`
  async search(@reqQuery('title') title: string = '') {
    const posts = await Post.find({
      title
    })

    return {
      posts
    }
  }

  @httpPost('/')
  // `req.body` (`@reqBody` does not accept property keys.)
  async create(@reqBody() body: unknown) {
    const validatedBody = validate(body)
    const post = await Post.create({
      ...validatedBody
    })

    return {
      post
    }
  }
}

We also provide reqHeaders, reqCookies and reqSession for req.headers, req.cookies and req.session. To know more, see our api documentation below.

Body validation

@reqBody supports validation via class-validator.

Please install class-validator package first.

npm install class-validator
import { IsString } from 'class-validator'

class PostDTO {
  @IsString()
  title: string

  @IsString()
  content: string
}


@controller('/posts')
class PostController() {
  @httpPost('/')
  // Tachijs can access `PostDTO` via reflect-metadata.
  async create(@reqBody() body: PostDTO) {
    // `body` is already validated and transformed into an instance of `PostDTO`.
    // So we don't need any extra validation.
    const post = await Post.create({
      ...body
    })

    return {
      post
    }
  }
}

Custom parameter decorators!

If you're using passport, you should want to access user data from req.user. @handlerParam decorator make it possible. The decorator gets a selector which accepts express's req, res and next. So all you need to do is decide what to return from thoes three parameters.

import { controller, httpGet, handlerParam } from 'tachijs'

@controller('/')
class HomeController {
  @httpGet('/')
  async showId(@handlerParam((req, res, next) => req.user) user: any) {
    doSomethingWithUser(user)

    return {
      ...
    }
  }
}

If you want reusable code, please try like the below.

import { controller, httpGet, handlerParam } from 'tachijs'

function reqUser() {
  // You can omit other next params, `res` and `next`, if you don't need for your selector.
  return handlerParam(req => req.user)
}

@controller('/')
class HomeController {
  @httpGet('/')
  async showId(@reqUser() user: any) {
    doSomethingWithUser(user)

    return {
      ...
    }
  }
}
Bind methods of req or res before exposing

You can also pass methods of req or res which are augmented by express module. Some of them might need the context of them. So please bind methods before exposing like the below example.

export function cookieSetter() {
  return handlerParam((req, res) => res.cookie.bind(res))
}
design:paramtype

Moreover, tachijs exposes metadata of parameters to forth argument. So you can make your custom validator for query with class-transformer-validator like below. (req.body is also using this.)

import { controller, httpGet, handlerParam } from 'tachijs'
import { IsString } from 'class-validator'
import { transformAndValidate } from 'class-transformer-validator'

function validatedQuery() {
  return handlerParam((req, res, next, meta) => {
    // meta.paramType is from `design:paramtypes`.
    // It is `Object` if the param type is unknown or any.
    return meta.paramType !== Object
      ? transformAndValidate(meta.paramType, req.query)
      : req.query
  })
}

// Validator class
class SearchQuery {
  @IsString()
  title: string
}

@controller('/')
class PostController {
  @httpGet('/search')
  // Provide the validator class to param type.
  // tachijs can access it via `reflect-metadata`.
  search(@validatedQuery() query: SearchQuery) {
    // Now `query` is type-safe
    // because it has been validated and transformed into an instance of SearchQuery.
    const { title } = query

    return {
      ...
    }
  }
}

To know more, see @handlerParam api documentation below.

Redirection, Rendering via pug and others...

Techinically, you don't have to access res to response data. But, if you want to redirect or render page via pug, you need to access res.redirect or res.render. Sadly, if you do, you have make mockup for res.

But, with tachijs, you can tackle this problem.

import { controller, httpGet, RedirectResult } from 'tachijs'

@controller('/')
class HomeController {
  @httpGet('/redirect')
  redirectToHome() {
    return new RedirectResult('/')
  }
}

Now, you can test your controller like the below example.

describe('HomeController#redirectToHome', () => {
  it('redirects to `/`', async () => {
    // Given
    const controller = new HomeController()

    // When
    const result = controller.redirectToHome()

    // Then
    expect(result).toBeInstanceOf(RedirectResult)
    expect(result).toMatchObject({
      location: '/'
    })
  })
})

There are other results too, EndResult, JSONResult, RenderResult, SendFileResult, SendResult, and SendStatusResult. Please see our api documentation below.

BaseController

If you need to use many types of result, you probably want BaseController. Just import it once, and your controller can instantiate results easily.

import { controller, httpGet, BaseController } from 'tachijs'

@controller('/')
// You have to extend your controller from `BaseController`
class HomeController extends BaseController {
  @httpGet('/redirect')
  redirectToHome() {
    // This is identically same to `return new RedirectResult('/')`
    return this.redirect('/')
  }
}

BaseController has methods for all build-in results, Please see our api documentation below.

BaseController#context

You may want to share some common methods via your own base controller. But, sadly, it is not possible to use decorators to get objects from req or res and services provided by @inject.

To make it possible, we introduce context. Which expose req, res and inject method via context if your controller is extended from BaseController.

interface Context {
  req: express.Request
  res: express.Response
  inject<S>(key: string): S
}
import { BaseController, controller, httpPost } from 'tachijs'

class MyBaseController extends BaseController {
  async getUserConfig() {
    // When unit testing, `context` is not defined.
    if (this.context == null) {
      return new UserConfig()
    }

    const { req, inject } = this.context

    // Now we can get the current user from `req`
    const currentUser = req.user

    // And inject any services from the container.
    const userConfigService = inject<UserConfigService>(
      ServiceTypes.UserConfigService
    )

    return userConfigService.findByUserId(userId)
  }
}

@controller('/')
class HomeController {
  @httpGet('/settings')
  settings() {
    const userConfig = await this.getUserConfig()

    return this.render('settings', {
      userConfig
    })
  }
}

#httpContext, #inject and #injector will be deprecated from v1.0.0. Please use #context

Customize result

If you want to have customized result behavior, you can do it with BaseResult. BaseResult is an abstract class which coerce you to define how to end the route by providing execute method. (Every built-in result is extended from BaseResult.)

Let's see our implementation of RedirectResult.

import express from 'express'
import { BaseResult } from './BaseResult'

export class RedirectResult extends BaseResult {
  constructor(
    public readonly location: string,
    public readonly status?: number
  ) {
    super()
  }

  // tachijs will provide all what you need and execute this method.
  async execute(
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) {
    if (this.status != null) return res.redirect(this.status, this.location)
    return res.redirect(this.location)
  }
}

Dependency injection

To make controllers more testable, tachijs provides dependency injection.

Let's think we have some mailing service, MailerService. While developing or testing, we probably don't want our server to send real e-mail everytime.

import tachijs, {
  controller,
  httpGet,
  httpPost,
  reqBody,
  inject,
  BaseController
} from 'tachijs'

// Create enum for service types
enum ServiceTypes {
  EmailService = 'EmailService',
  NotificationService = 'NotificationService'
}

// Abstract class coerce MailerService must have `sendEmail` method.
abstract class MailerService {
  abstract sendEmail(content: string): Promise<void>
}

// Mockup service for development and testing.
class MockEmailService extends MailerService {
  async sendEmail(content: string) {
    console.log(`Not sending email.... content: ${content}`)
  }
}

class EmailService extends MailerService {
  async sendEmail(content: string) {
    console.log(`Sending email.... content: ${content}`)
  }
}

interface Container {
  [ServiceTypes.EmailService]: typeof MailerService
}

const envIsDev = process.env.NODE_ENV === 'development'

// Swapping container depends on the current environment.
const container: Container = envIsDev
  ? {
      // In development env, don't send real e-mail because we use mockup.
      [ServiceTypes.EmailService]: MockEmailService
    }
  : {
      [ServiceTypes.EmailService]: EmailService
    }

@controller('/')
class HomeController extends BaseController {
  constructor(
    // Inject MailerService. The controller will get the one registered to the current container.
    @inject(ServiceTypes.EmailService) private mailer: MailerService
  ) {
    super()
  }

  @httpGet('/')
  home() {
    return `<form action='/notify' method='post'><input type='text' name='message'><button>Notify</button></form>`
  }

  @httpPost('/email')
  async sendEmail(@reqBody() body: any) {
    await this.mailer.sendEmail(body.message)

    return this.redirect('/')
  }
}

const server = tachijs({
  controllers: [HomeController],
  // Register container
  container
})

So you can test HomeController#sendEmail like the below example.

describe('HomeController#sendEmail', () => {
  it('sends email', async () => {
    // Given
    const spyFn = jest.fn()
    class TestEmailService extends MailerService {
      async sendEmail(content: string): Promise<void> {
        spyFn(content)
      }
    }
    const controller = new HomeController(new TestEmailService())

    // When
    const result = controller.sendEmail('hello')

    // Then
    expect(spyFn).toBeCalledWith('hello')
  })
})

Now we don't have to worry that our controller sending e-mail for each testing.

Furthermore, you can inject other services to your service as long as they exist in the container.

class NotificationService {
  constructor(
    // When NotificationService is instantiated, MailerService will be instantiated also by tachijs.
    @inject(ServiceTypes.EmailService) private mailer: MailerService
  ) {}

  async notifyWelcome() {
    await this.mailer.sendEmail('Welcome!')
  }
}
DI without tachijs

When some testing or just writing scripts using services, you might want to use DI without tachijs function. So we exposed Injector class which is used by tachijs.

enum ServiceTypes {
  NameService = 'NameService',
  MyService = 'MyService'
}
class NameService {
  getName() {
    return 'Test'
  }
}
class MyService {
  constructor(
    @inject(ServiceTypes.NameService) private nameService: NameService
  ) {}

  sayHello() {
    return `Hello, ${this.nameService.getName()}`
  }
}
const container = {
  [ServiceTypes.NameService]: NameService,
  [ServiceTypes.MyService]: MyService
}

// Create injector
const injector = new Injector(container)

// Instantiate by a key
const myService = injector.inject<MyService>(ServiceTypes.MyService)
// Instantiate by a constructor
const myService = injector.instantiate(MyService)

Bad practices

Please check this section too to keep your controllers testable.

Execute res.send or next inside of controllers or @handlerParam

Please don't do that. It just make your controller untestable. If you want some special behaviors after your methods are executed, please try to implement them with BaseResult.

Do

class HelloResult extends BaseResult {
  async execute(
    req: express.Request,
    res: express.Response,
    next: express.NextFunction
  ) {
    res.send('Hello')
  }
}

class HomePageController extends BaseController {
  @httpGet('/')
  index() {
    // Now we can test it by just checking the method returns an instance of `HelloResult`.
    return new HelloResult()
  }
}

Don't

class HomePageController {
  @httpGet('/')
  index(@handlerParam((req, res) => res) res: expressResponse) {
    // We have to make mock-up for express.Response to test
    res.send('Hello')
  }
}

Access BaseController#context in your descendant controllers

It is designed to be used inside of your base controller to make unit testing easy.

Do

class MyBaseController extends BaseController {
  doSomethingWithContext() {
    if (this.context == null) {
      // on unit testing
      return
    }
    // on live
  }
}

Don't

class HomePageController extends MyBaseController {
  @httpGet('/')
  index() {
    // We have to make mock-up everything to test
    this.context!.req....
  }
}

APIs

tachijs(options: TachiJSOptions): express.Application

Create and configure an express app.

TachiJSOptions

interface TachiJSOptions<C = {}> {
  app?: express.Application
  before?: ConfigSetter
  after?: ConfigSetter
  controllers?: any[]
  container?: C
}

type ConfigSetter = (app: express.Application) => void
  • app Optional. If you provide this option, tachijs will use it rather than creating new one.
  • before Optional. You can configure express app before registering controllers for applying middlewares.
  • after Optional. You can configure express app before registering controllers for error handling.
  • controllers Optional. Array of controller classes.
  • container Optional. A place for registered services. If you want to use DI, you have to register services to here first.

@controller(path: string, middlewares: RequestHandler[] = [], routerOptions: RouterOptions = {})

It marks class as a controller.

  • path Target path.
  • middlewares Optional. Array of middlewares.
  • routerOptions Optional. Express router options.

@httpMethod(method: string, path: string, middlewares: RequestHandler[] = [])

It marks method as a request handler.

  • method Target http methods, 'get', 'post', 'put', 'patch', 'delete', 'options', 'head' or 'all' are available. ('all' means any methods.)
  • path Target path.
  • middlewares Optional. Array of middlewares.

tachijs also provides shortcuts for @httpMethod.

  • @httpGet(path: string, middlewares: RequestHandler[] = [])
  • @httpPost(path: string, middlewares: RequestHandler[] = [])
  • @httpPut(path: string, middlewares: RequestHandler[] = [])
  • @httpPatch(path: string, middlewares: RequestHandler[] = [])
  • @httpDelete(path: string, middlewares: RequestHandler[] = [])
  • @httpOptions(path: string, middlewares: RequestHandler[] = [])
  • @httpHead(path: string, middlewares: RequestHandler[] = [])
  • @httpAll(path: string, middlewares: RequestHandler[] = [])

@handlerParam<T>(selector: HandlerParamSelector<T>)

  • selector selects a property from req, res, next or even our meta
export type HandlerParamSelector<T> = (
  req: express.Request,
  res: express.Response,
  next: express.NextFunction,
  meta: HandlerParamMeta<T>
) => T
interface HandlerParamMeta<T> {
  index: number
  selector: HandlerParamSelector<T>
  paramType: any
}
  • index Number index of the parameter.
  • selector Its selector.
  • paramType metadata from design:paramtypes.

@reqBody(validator?: any)

Inject req.body.

  • validator Optional. A class with decorators of class-validator. tachijs will validate req.body with it and transform req.body into the validator class. If validator is not given but the parameter has a class validator as its param type, tachijs will use it via reflect-metadata.
import { controller, httpPost, reqBody } from 'tachijs'

@controller('/post')
class PostController {
  @httpPost('/')
  // Identically same to `create(@reqBody(PostDTO) post: PostDTO)`
  create(@reqBody() post: PostDTO) {
    ...
  }
}

@reqParams(paramName?: string)

Inject req.params or its property.

  • paramName If it is given, req.params[paramName] will be injected.

@reqQuery(paramName?: string)

Inject req.query or its property.

  • paramName If it is given, req.query[paramName] will be injected.

@reqHeaders(paramName?: string)

Inject req.headers or its property.

  • paramName If it is given, req.headers[paramName] will be injected.

@reqCookies(paramName?: string)

Inject req.cookies or its property.

  • paramName If it is given, req.cookies[paramName] will be injected.

@reqSignedCookies(paramName?: string)

Inject req.signedCookies or its property.

  • paramName If it is given, req.signedCookies[paramName] will be injected.

@cookieSetter()

Inject res.cookie method to set cookie.

@cookieClearer()

Inject res.clearCookie method to clear cookie.

@reqSession(paramName?: string)

Inject req.session.

BaseController

A base for controller which have lots of helper methods for returning built-in results. Also, it allows another way to access properties of req, res and inject without any decorators.

  • #context tachijs will set req, res and inject method to this property. So, when unit testing, it is not defined.
    • #context.req Raw express request instance
    • #context.req Raw express response instance
    • #inject<S>(key: string): S A method to access a registered service by the given key. It is almost same to @inject decorator. (@inject<ServiceTypes.SomeService> someService: SomeService => const someService = this.inject<SomeService>(ServiceTypes.SomeService))
  • #end(data: any, encoding?: string, status?: number): EndResult
  • #json(data: any, status?: number): JSONResult
  • #redirect(location: string, status?: number): RedirectResult
  • #render(view: string, locals?: any, callback?: RenderResultCallback, status?: number): RenderResult
  • #sendFile(filePath: string, options?: any, callback?: SendFileResultCallback, status?: number): SendFileResult
  • #send(data: any, status?: number): SendResult
  • #sendStatus(status: number): SendStatusResult

Results

BaseResult

All of result classes must be extended from BaseResult because tachijs can recognize results by instanceof BaseResult.

It has only one abstract method which must be defined by descendant classes.

  • execute(req: express.Request, res: express.Response, next: express.NextFunction): Promise<any> tachijs will use this method to finalize response.

new EndResult(data: any, encoding?: string, status: number = 200)

tachijs will finalize response with res.status(status).end(data, encoding).

new JSONResult(data: any, status: number = 200)

tachijs will finalize response with res.status(status).json(data).

new NextResult(error?: any)

tachijs will finalize response with next(error).

new RedirectResult(location: string, status?: number)

tachijs will finalize response with res.redirect(location) (or res.redirect(status, location) if the status is given).

new RenderResult(view: string, locals?: any, callback?: RenderResultCallback, status: number = 200)

tachijs will finalize response with res.status(status).render(view, locals, (error, html) => callback(error, html, req, res, next))

type RenderResultCallback = (
  error: Error | null,
  html: string | null,
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) => void

new SendFileResult(filePath: string, options: any, callback?: SendFileResultCallback, status: number = 200)

tachijs will finalize response with res.status(status).sendFile(filePath, options, (error) => callback(error, req, res, next))

type SendFileResultCallback = (
  error: Error | null,
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) => void

new SendResult(data: any, status: number = 200)

tachijs will finalize response with res.status(status).send(data).

new SendStatusResult(status: number)

tachijs will finalize response with res.sendStatus(status).

@inject(key: string)

Inject a registered service in container by the given key.

class Injector

new Injector<C>(container: C)

Instantiate an injector with container

#instantiate(Constructor: any): any

Instantiate a service constructor. If the constructor has injected services, this method instantiate and inject them by #inject method.

#inject<S = any>(key: string): S

Instantiate a service by a key from Container. If there is no service for the given key, it will throws an error.

License

MIT © Junyoung Choi

boostnote-legacy's People

Contributors

amedora avatar antogin avatar asmsuechan avatar awolf81 avatar bimlas avatar daiyam avatar dojineko avatar dredav avatar duartefrazao avatar ehhc avatar enyaxu avatar gregueiras avatar hikerpig avatar kazup01 avatar kohei-takata avatar luisreinoso avatar nathan-castlehow avatar paulrosset avatar pfftdammitchris avatar rayou avatar rokt33r avatar roottool avatar simon-the-sorcerer avatar sosukesuzuki avatar sota1235 avatar voidsatisfaction avatar yosmoc avatar yougotwill avatar ytk141 avatar zerox-dg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

boostnote-legacy's Issues

Quit Boostnote unexpectedly

Every time I close Boostnote with shortcut cmd + W , The window comes up which says Boostnote quits unexpectedly. While, I can close it correctly when I push window's red close button.

Keep Boostnote opened in background

Great app! I love it but there's a missing feature for me that would make it perfect in my opinion.
It would offer the way to quickly display notes without having Boostnote launched in the dock. Closing Boostnote with the cross or with Cmd+W would only close the main window but the app would continue working in background.
An additional feature would be to allow starting the app in background. In this way, I would everytime launch boostnote on startup.

Visual distortion

Hi,
i have interesting problem. When i move the cursor random partition of boostnote app going to dark.
Here is a screenshot
screen shot 2016-05-24 at 09 04 10

Thank you.

Select code Syntax - Markdown option only

I've downloaded latest Windows version and I'm only seeing "Markdown" option in the select code syntax menu. Is something wrong? Should I add other syntax separately?

imagen_258

Homebrew formula

Hi

I forked Boostnote to create a cask, but I have no idea of what auth_code.json is. Could you tell me what's that and how can I have one? My idea is to provide a /releases/ folder with osx versions, and the homebrew formula.

Thanks

Backup feature & Evernote?

Are you planning to add the Backup feature for this application? It would be nice if I can backup the posts easily and maybe with auto synchronisation to evernote account?

Backup

How i can export and import the data in other PC??

Thanks,

ショートカットキーを変更したのに残ってしまう。

SettingからToggle Finder(popup)の変更を行いました。
はじめは、cmd + Tで行い、それがPC全体に適用されるものだと知り、他のものに変更し、Saveしましたが、未だcmd + Tでboostnoteの検索が開かれてしまいます。
気に入っていたので、解決策 or 修正を行ってもらえると嬉しいです。

bug report: an error occurred when updating the app

@Rokt33r
Hi, i love your Boostnote.app, and i wanna thank you so much for your contribution on this app.
when i updated the app (from 0.5.11 to 0.5.12), an error occurred.

2016-05-16 19 33 38

after rebooting the app, the update was completed.
Now i can use Boostnote(0.5.12) without any incident.
i don't know what it is but want to share this bug report.

at last, my environment is as follows:

  • Mac yosemite (10.10.5)
  • node.js (v0.10.40)

(WIP)Restruction plan

Using react-css-modules (v0.6.*)

Currently, stylesheets in Boostnote has been compiled separately from React Components.
It makes hard to manage style codes. If I remove a component, I have to find and delete the style code related it. It is so tiresome and annoying task. Therefore, there must be some unnecessary codes I haven't noticed.

To solve this problem, I'd been trying several CSS library for React app and react-css-modules seems to be the best.

From v0.6.* every React component in Boostnote will use react-css-modules.

Simplify state2prop function of Redux (v0.6.*)

As you see, the remap function in HomePage component looks so horrible. It is because I wasn't good at using Redux at that time.
I'm currently re-writing it overall and it will be shipped on v0.6.*.

Decouple app structure into backend and frontend

The current build is highly consolidated. While react stuffs are comming from webpack, moment, markdown-it are comming from node_modules. It makes hard to understand.

The solution is split backend code and frontend code.

Another benefit of this is that users will be able to host boostnote on internet.(I think it would be quite awesome.)

Search / Create notes from menubar menu

Hello.

I recently found this application (https://www.renfei.org/snippets-lab/) and I really like their "SnippetsLab Assistant" which allows you to search and create new notes directly from the menu bar / tray menu.

Example

Boostnote have finder which have similar features but its not working in all OS and also some people might prefer to use the menu.

I think it would be nice to have something similar in Boostnote.
There are many menubar examples built with Electron, so I think it wouldn't be too hard to implement.

Also maybe you can take some ideas from "Snippets Lab" about Gist integration.

Auto-insert bullet in list

When writing an unordered list in markdown, Boostnote inserts a new asterisk as you hit return in the list. But, if you hit return twice (to start writing new content outside the list) that last asterisk should delete itself.

This would really help with productivity while writing!

Wrong markdown syntax highlighting when specifying source language

When I specify the source language it sometimes gets confused with syntax highlighting, although the preview displays the code correctly.
This usually happens when the code is right bellow a list:
example 1

But then the issue is resolved once you insert a line bellow the list item
example 2

I'm using 0.4 on ubuntu

Tab in list / sublist

A good idea should be to grab the Tab key when in list mode to create a sublist in the list.

  • list
  • list
    • sublist addes with Tab instead of adding 2 space then a star
  • Shift+Tab to return to the main list

The idea is to match the mechanics of https://stackedit.io/editor# maybe ?

Checkboxes not shown in shared note

Hello,
I found that checkboxes are not shown when I see shared notes on browser.

image

When I edit notes in Boostnote, they are surely shown.

Thank you.

Group folders into Books

My suggestion is to somehow add the possibility to make separate "books" or "notebooks" or "groups" or "tabs" (or another name) of folders & tags. Only one "book" would be open at the time. It would allow to group things separately, for example:

  • code snippets
  • software notes
  • personal notes
  • recipes

When I open "recipes" for example, I would only see the folders and tags of "recipes". (And search would only work on "recipes", as an option.) That way Boostnote would be more of a universal note taking application.

Small GUI bug with scrollbars

Hello. I found a non-critical gui collision. Maybe you already know about this bug, but anyway.
I'm working on Ubuntu 14.04 / 64
Thanks for your great work!

little_bug

Best regards, Stephan

Repository plan

Repository plan

User will be able to select any folder to use as a note repository from v0.6.*.
It has been designed to be handled by Git properly.

root
 |- data
     |-note1.cson
     |-note2.cson
     |-note3.cson
 |- boostrepo.json

boostrepo.json

{
  name: String,
  author: String, // Same convention of package.json, `John Doe <[email protected]> (http://example.com)`
  remotes: [{
    name: String,
    url: String, // url of git remote
    branch: String // if branch isn't set, it will try to use `master` branch.
  }],
  folders: [{
    key: String // Unique sha1 hash key to identify folder,
    name: String,
    color: String // All CSS color formats available.
  }]
}

data directory

Every note will be saved here as a single CSON file to git diff efficiently.

This is because CSON supports Multiline string.

File name of each cson file will be used to identify note.
Commonly, Boostnote will automatically generate sha1 key and use it as a file name when creating a new note.

note.cson

name: String
tags: [String] // tags
folder: String // hash key of folder
mode: String // syntax mode
title: String
content: String
createdAt: Date
updatedAt: Date

drag and drop

when i drag & dropped some .java files and .clj files, boost turns into a single-window text viewer.
ie, no edit possible and no menu cannot be selected except for help menu.
i wanted to reset the configuraion, but restarting won't do anything changed, so i had to uninstall. :(
my os is win 8.1 home 64bit. anyone having same issue?

Code block withs line numbers

I have added a line numbering to the code blocks in the markdown preview:

lazytyper@891cd31

If the changes are ok, I will send a pull request.

  • The gitter maybe should be able to be turned off in the settings.

I would also implement a copy-button: The button will be displayed only when the mouse is over the code, when you click on the button, the code will be copied into the clipboard.

Does Markdown footnotes support?

마크다운 각주 기능은 지원되지 않는건가요? 버그인가요?
[^~], [^~]:를 써봐도 되지 않습니다.

Boostnote는 유용하게 쓰고 있습니다 감사합니다

about Linux app

AUR Package for Arch Linux

You can install Boostnote with the command below.

git clone https://aur.archlinux.org/boostnote.git
cd boostnote
makepkg -si

Planned target

Target OS

  • Arch(AUR)
  • Ubuntu(PPA)
  • Fedora

Target Desktop Environment

  • Unity
  • Xfce
  • Gnome
  • Cinnamon

v0.6.* will be release on this week(~7/16).

Specific progress

  • New UI
  • Note storage can be placed anywhere in File system.
  • Markdown note
  • Snippet note(New feature)
  • Search popup
  • Migrations
  • Functional testing on other OS

7/21
screen shot 2016-07-21 at 3 22 27 am

7/20
Another type of note : Snippet Note
screen shot 2016-07-20 at 12 36 46 am
It will be compatible with Gist

7/19

Layout changed
screen shot 2016-07-19 at 1 26 22 pm

The new app will provide 2 types of note.
screen shot 2016-07-19 at 10 48 37 am

7/18
I finished the code of switching preview/editor on focus/blur.
Currently working on auto scrolling for markdown preview.(DONE 2016/7/19)
After then, I'll working on snippet note format.

7/16
I'm really sorry, It will be postponed again. I'd have to deal with some urgent problem within a couple of day(7/15~16). So, it will be finished several days later. Till it done, I'll report the progress this issue.
I apologize again. this is because that I am the only developer in this project.


7/14
70%
screen shot 2016-07-14 at 2 00 27 pm

7/13
Preferences modal(I didn't push yet)
screen shot 2016-07-13 at 3 44 50 am
screen shot 2016-07-13 at 3 48 24 am
screen shot 2016-07-13 at 3 44 52 am

7/11
I'm sorry to being late again. Currently, I'm doing my best to deal with it.
It will be released on this week by any means.


Sorry for making wait you. I'd been so busy during June.
Anyway, the new app will be release on this Sunday(7/10).

Which CSS library would be great for React app?

Currently this app using stylus for styling. As increasing Component, code structure has became hard to grasp.

To deal with this problem, I'll try Radium.
It seems to be elegant. Only problem is that decorator syntax is required.

I need your opinion.

Web App

Are there any plans for having a web app for Boostnote so that you can access & create your notes online from any device like in Evernote?

Feature Requests

Hey,
Boostnote looks great but though I'm missing cloud storage, android version and note encryption.

Would appreciate to see them implemented

cheers.

Benny

about Zooming UI

Putting Zoom select on status bar of GitKraken is quite brilliant design. It can be found easily while not annoying.
I'll try to imitate this feature.
screen shot 2016-05-11 at 8 01 22

Known bugs

  • Press ESC to watch the preview message hampers editting the bottom line of editor screen

Mobile app

I was at the readme file of the 0.6.0 version and this caught my attention:

Target OS : OSX, Windows, Linux(also mobile somewhen!)

So I wanted to know if there is anybody involved in the making of a mobile app.

I would like to get involved, I ran the 0.6.0 version but I didn't understand well how the user experience should be. So I suppose that I will have to wait until the release, but in the meanwhile I would like to know if anybody has ideas or thoughts on this.

Edit: I think I'll get started with an app that is like the pre 0.6.0 version

Export Notes

Is it possible to export your notes to markdown, html, microsoft word, pdf?

Cannot use checkboxes without editing markdown

Hello Boostnote people!

I discovered recently that when you go to click a checkbox it opens up markdown view instead of changing the checkbox.

Check box items
screen shot 2016-04-08 at 6 43 35 pm

Immediately after attempting to click one
screen shot 2016-04-08 at 6 43 43 pm

Clicking the third box would normally result in
screen shot 2016-04-08 at 6 47 57 pm

And should alter markdown to be this automatically
screen shot 2016-04-08 at 6 48 04 pm

This is just something I came across when checking off on a todo list. Obviously this isn't a general todo app, but it would be nice if this functionality could happen.

Thanks!
-Petroochio

Not putting special characters right

My native language is spanish, and when I want to insert a special character in my Mac with english keyboard, I press and hold the letter and then enter a number (that is shown in a little window above the cursor). For example: for 'á' I press and hold 'a' and then enter number 2.

In Boostnote it enters 'aá' instead of 'á' only.

Link to another note

I think it would be great to be able to link to another note you have saved. With this, you could create one note that acts as a master list and link it to other notes for finer detail. I would assume when you click the link it would just switch to that note or open it in a split panel.

screen shot 2016-05-31 at 12 03 03 pm
screen shot 2016-05-31 at 12 02 16 pm

Todo List

  • Platform
    • Windows 32bit
    • Linux
      • Ubuntu
      • Arch Linux
  • Test suite
    • Component test
    • E2E test
  • Restructure code
  • Improve document
    • Comments in code
    • contributing.md
    • readme.md
  • Renew Landing

HMR server gives error

Hi,
When i run npm run webpack i get the error below. What is this ?

ERROR in ./~/css-loader!./~/stylus-loader!./browser/styles/main/index.styl
Module build failed: TypeError: /Users/metinguler/Desktop/electron/Boostnote/browser/styles/main/index.styl:159:1
   155|     opacity 0.7
   156|     &:hover
   157|       opacity 1
   158|       background-color lighten(brandColor, 10%)
   159| 
--------^

Path must be a string. Received undefined

    at assertPath (path.js:7:11)
    at extname (path.js:1433:5)
    at new SourceMapper (/Users/metinguler/Desktop/electron/Boostnote/node_modules/stylus/lib/visitor/sourcemapper.js:41:7)
    at Renderer.render (/Users/metinguler/Desktop/electron/Boostnote/node_modules/stylus/lib/renderer.js:94:9)
    at /Users/metinguler/Desktop/electron/Boostnote/node_modules/stylus-loader/index.js:149:12
    at tryCatchReject (/Users/metinguler/Desktop/electron/Boostnote/node_modules/when/lib/makePromise.js:840:30)
    at runContinuation1 (/Users/metinguler/Desktop/electron/Boostnote/node_modules/when/lib/makePromise.js:799:4)
    at Fulfilled.when (/Users/metinguler/Desktop/electron/Boostnote/node_modules/when/lib/makePromise.js:590:4)
    at Pending.run (/Users/metinguler/Desktop/electron/Boostnote/node_modules/when/lib/makePromise.js:481:13)
    at Scheduler._drain (/Users/metinguler/Desktop/electron/Boostnote/node_modules/when/lib/Scheduler.js:62:19)
 @ ./browser/styles/main/index.styl 4:14-130 13:2-17:4 14:20-136

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.