Giter Club home page Giter Club logo

boostnote.next-local'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.next-local's People

Contributors

alexandreaguido avatar butterycrumpet avatar davy-c avatar dependabot[bot] avatar ellekasai avatar flexo013 avatar funzin avatar georgeherby avatar guneskaan avatar hsuanxyz avatar igorsakalo avatar jhdcruz avatar jongkeun avatar komediruzecki avatar laudep avatar lexar-systems avatar marilari88 avatar matthfaria96 avatar pfftdammitchris avatar pokidokika avatar rohjs avatar rokt33r avatar sarah-seo avatar saxc avatar siromath avatar tetsuya95 avatar tobifasc avatar xatier avatar ymtdzzz 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

boostnote.next-local's Issues

Program icon

Current behavior

https://i.imgur.com/gsHNY7w.png

As cam be seen in the screenshot, Boostnote doesn't use the icon assigned to it, neither in the Task Switcher, nor as the program's icon.

Expected behavior

At first I thought it was .AppImage behaviour, as I don't use many .AppImages. But then I noticed it's working more than 100% fine with another .AppImage that I use. So it's obviously not that.

Steps to reproduce

Open the program and switch about. Alt+Tab

Environment

  • Boost Note.next version: 0.22.3
  • OS version and name: Manjaro Linux 21.2rc Qonos, KDE.

Scroll Bar is Difficult to See

Current behavior

I'm using material as Editor Theme, neo as Markdown Code Block Theme. The monitor is 27" 2K, it seems that the scroll bar is very difficult to see or locate (too thin and too light), shown as the following image:

Image 003

Both happens in Boost Note.Next and Boost Note.Next.Local

Expected behavior

Shows better. Better color / better size.

Environment

  • Boost Note.next version: 0.21.1
  • OS version and name: Windows 10 64 bit

Opening Existing Notes Locally - Only Shows Folder Structure without the Notes

Current behavior

I am currently unable to retrieve my notes that are saved locally. I can see the .json files with the content when I browse in Finder but BoostNote is unable to load them. It is only able to load the folder structure.

Screen Shot 2022-01-12 at 10 58 40 AM

Despite, multiple reloads of the application (full quit + re-open) and despite removing space and re-adding it, the notes are still not showing up.

IndexDB is showing up as empty
Screen Shot 2022-01-12 at 11 01 15 AM

Local Storage has content:

id: "..."
location: "/Users/xyz/My Folder Drive/MyNotes"
name: "Local"

Expected behavior

Expecting a way to reset / reload so the notes start showing up again..

Steps to reproduce

This started happening after an update to the sync software where the .json files are present. However, the files are still there and readable.

Environment

  • Boost Note.next version: Version 0.22.3 (0.22.3)
  • OS version and name: macOS Big Sur 11.6.2

Collappsible detail areas

This is a re-opening of the Collapsible detail areas ticket that was opened before the local/cloud split. So seeing as it's the exact same thing, I'll copy and paste it here.

Current behavior

This is not a bug. Well, I don't think so, anyway.

I can''t remember if it was like this in the previous version, so if it wasn't, then just ignore my ramblings.

What I'm rambling on about is the collapsible details area you find on the Discouse forums, and since it uses Markdown as well, I thought Implementing it would be swell.

Expected behavior

Typing an expanded details section should work. AFAIK it's:

[detail="a heading"]
Some hidden text.
[/detail]

Currently, I use the HTML <details> tag to accomplish this, but it feels...wrong...I don't know how else to describe it. Like it shouldn't have to be this way.

Steps to reproduce

Use

[detail="a heading"]
Text
[/detail]

Environment

Manjaro Linux. Not sure it's applicable, or necessary for this, but there it is anyway.

  • Boost Note.next-local version: 0.21.1
  • OS version and name: Manjaro Linux 21.1.0

Feature: Heatmap or Calendar in sidebar

Feature Request

Could we have Heatmap or Calendar widgets in the side bar?

  • heatmap: Github style heatmap which shows different colors as per count of the notes for a specific day
  • Calendar: Fill the box with different colors as per count of the notes for a specific day

app_with_heatmap
app_with_calendar

Boost Note 0.21.1 doesnt work at all!

Current behavior

Clicking on file and then the following menu options doesn't do anything:
New Note
New Folder
Save As
Add Cloud Space
Add Local Space

The only options which works is "Preferences"

Also the following menu is completely empty when clicking it:
Screenshot from 2021-08-19 08-37-21

Expected behavior

Open the menus. Do something.

Steps to reproduce

  1. Download .deb file
  2. install
  3. run

Environment

  • Boost Note.next version: 0.21.1
  • OS version and name: Ubuntu 20.04

BoostNote cannot auto reload when the file in disk has been changed!!!

Current behavior

BoostNote cannot auto reload when the file in disk has been changed!!!

Expected behavior

Auto reload the content of note file when the file in disk has been changed.

Steps to reproduce

  1. Open the app.
  2. Modify note's content in disk with vscode
  3. The app shows the old content,

Environment

Desktop/Web

  • Boost Note.next version: 0.13.2
  • OS version and name: macos

Notes missing in latest version

I am on linux, and installed
Boost Note 0.21.1 (local)
as I was looking to upgrade from
Boost Note 0.19.0.

I only use local storage, but in 0.21.1 local version, I see random notes are from the left-side view. If I open 0.19.0, it shows the notes just fine. Oddly if I search for the note, it shows it in the search, and can open. It sees the correct path at the top of the window for the breadcrumb view, but the left panel still does not show the page consistently. I Installe dmatching versions on a second home system, and pointed it to the same file store, and it behaves the same, with 0.21.1 not showing the same files (but able to search/display them), while 0.19.0 shows them correctly.

Color Text -

Hi,

I can add a simple HTML marker to the text like bold but when I tried to change the text colour nothing is working.
is it a limitation of the Boost Note local we don't have the Sanitizazion option?

I want a feature render text in special color:

#347E5E -->

#347E5E

Environment

  • Boost Note - Local 0.21.1
  • OS version and name: Mac os Big sur

Fix menus and keyboard shortcut

Some of the shortcuts are not working.

Focus title

Clicking on items in menus (select all, redo)

Switching to undefined workspace gives not found page

No editor newlines shown (UI improvement)

Current behavior

  • No editor newlines shown
  • Users can be easily confused with having a newline when typing 'enter' and the preview does not show a newline

Expected behavior

  • Show if there is a newline character at the end of the line (optional on/off)

Environment

  • Boost Note.next version: 0.21.1
  • OS version and name: All (Windows, macOS, Linux)

Unable to add tag to note if tag ends in #

Current behavior

A tag ending in '#', 'C#' for example, is not accepted when trying to add the tag to a note.

image

Expected behavior

A tag ending in '#' should be created.

Steps to reproduce

  1. Enter tag ending in '#'.
  2. Press return.
  3. The application does not respond.

Environment

  • Version : 0.1.4
  • OS Version and name : Windows 10

Snippet App?

According to the 2020 roadmap, the old Boostnote snippet functionality will be separated out as a seperate app.

Is this still the plan? I am currently using the old version because I use Boostnote exclusively for code snippets.

Thanks!

Interacting with checkboxes repositions editor view to the top

Current behavior

In longer notes that also have checkboxes lower down, interacting with the checkbox causes the editor to scroll back up to the top of the page.

See here for before/after shots: https://imgur.com/a/trK710B

I didn't have the dev tools open in the first because it didn't seem to be showing anything, but I opened them later.

Expected behavior

Interacting with a checkbox should not cause the editor view to change.

Steps to reproduce

  1. Make a note with several paragraphs of text
  2. put a checkbox in it
  3. scroll editor and viewer to checkbox
  4. check the box and observe the behavior
  5. you can also uncheck the box and it will do it again

Environment

  • Boost Note.next version: 0.22.3
  • OS version and name: Ubuntu 21.10

Ctrl++ for zoom in does not work on Ubuntu 21.10

Current behavior

The shortcut Ctrl++ does not work, but Ctrl+- does (for zoom out). Zooming in can only be accomplished via the View menu.

Expected behavior

The shortcut should work :)

Steps to reproduce

  1. Press Ctrl++ to zoom in

Environment

  • Boost Note.next version: 0.22.3
  • OS version and name: Ubuntu 21.10

Thank you for your work maintaining Boostnote!

Mobile verison?

Hi, do you also plan to release a mobile app/web app version of boostnote local? That would be the ideal combination for note taking for me - use any cloud you trust, use this cloud to sync the notes (and make them available offline!), and enjoy the great UI of BoostNote.

I haven't seen any mention of a mobile app on the roadmap...

Note tag listing

Current behavior

When adding tags to a note, the listing of available tags is incomplete or wrong. Most of the times is doesn't list a tag I use a lot. Not every note, but close.

Expected behavior

When listing tags, while typing show all tags that has the characters being typed.

Steps to reproduce

Add multiple tags to a note.

Environment

Manjaro Linux. Happened on Boostnote next 0.20.x (possibly earlier, can't remember specifically. But I think it was fine in the Boostnote before Boostnote next) as well as Boostnote next local 0.21.1.

Desktop/Web

  • Boost Note.next version: 0.20.x onwards
  • OS version and name: Manjaro Linux 21.1.0

Add space location open

It would be beneficial to open space location from preferences

Same API as for export open destination location

Help -> Learn More goes to a URL (https://boosthub.io/) that doesn't appear functional

Current behavior

When navigating the menu items and selecting Help -> Learn More, a browser window opens to https://boosthub.io/. This URI appears to be nonfunctional at this time. I'm not sure if this is a transitory issue, or if the URI needs to be updated.

Selecting this:
boost2

Results in:
boost

Expected behavior

A URI is launched to an area where I can, in fact, learn more. Perhaps updated to https://boostnote.io/ ?

Steps to reproduce

Select Help -> Learn more from the current version of Boost Note.

Environment

Desktop

  • Boost Note.next version: 0.20.2
  • OS version and name: Windows 10 Pro

Add Table of Contents and its link following

Ported from old repo

New behavior

The ability to make a link to a heading within a note.

Example

Clicking the word tables in the preview will jump to the linked heading #Tables.

As you can see in the section about [tables](#Tabels).

# Tables
Foo bar...

Remove all archived documents

how to empty archive after deleting a lot of documents (local space)

Environment

Desktop

  • Boost Note.next version: <0.20.1>
  • OS version and name:

Display local space information

Current behavior

There is no UI displaying local space's information like its actual location.

Expected behavior

Should show it from preferences or introduce a storage setting page.

Environment

  • Boost Note.next version: v0.21.1
  • OS version and name:

I can't add an external storage from Synology NAS

Current behavior

Expected behavior

Hi everyone 👋

I use boostnote daily and I would like to share my notes between my devices (laptop and desktop).

But, I didn't add external storage from my Windows 10 pro :/

Steps to reproduce

  1. Open the folder shared from File Explorator (example: \\Your_Synology_NAS\Note_Shared
  2. Open Boostnote application and click on Create a space
  3. Then, click on Create a local space without creating an account
  4. Define the space type on File System, then click on Select Folder and select the folder shared \\Your_Synology_NAS\Note_Shared
  5. Then, click on Create Space button and you have the error (see the screen shot below). Here is the error message : Something went wrong - TypeError: Cannot read property '/' of undefined

image

Environment

  • Boostnote version: 0.14.1
  • OS version and name: Windows 10 Professionnel / 20H2 release

Thank you 😃

Implement tabs or separate windows

The separate windows feature is almost done/implemented
Update the code and/or investigate if everything works fine or implement tabs

Surrounding of text with quotes, brackets, backticks etc.

Current behavior

There does not appear to be a way to surround selected text in quotes or other markdown characters like *,$,` .

Expected behavior

  1. Select text.
  2. Type the character you want to surround text with, for example "[".
  3. "[" is then added before selected text and the bracket is closed with "]" at the end of selected text.

Environment

  • Boost Note.next version: 0.22.3
  • OS version and name: macOS Monterey 12.2.1

Improve search consistency and search options

Current behavior

At present, there is a simple "Search/Findbar", however this is not enough.
Why not expand it advanced like "regex search, Find and Replace options".
Its very hard to search as number of notes are increasing in number.

Expected behavior

I am proposing a entire and new revamp of Search box. I hope this may get fulfilled.

  1. The default behavior should be search all "title and notes" simultaneously.
    Also, a checkbox or an option to select, so it only searches only titles of notes, instead of contents and notes simultaneously.
  2. In addition to (1) point,
    Search bar should include features like "regex search, Find and Replace options" etc, this makes it more usable.

Steps to reproduce

This is not a bug, but a feature request.

Desktop/Web

  • Boost Note.next version: 0.9.0
  • OS version and name: Xubuntu 18

Add keystroke markdown

Current behavior

Currently, when adding a keystroke to a note, for example Up I have to use the HTML <kbd></kbd> tags.

Expected behavior

While this works fine, it would be much quicker to have a Markdown tag for it. I suggest double pipes. For example:

||Up||

Additionally

Additionally, it would also be awesome if the keystrokes are then displayed a bit differently thann the current text. Say, 110% size, in bold. Or something like that.

Environment

  • Boost Note.next version: 0.22.3
  • OS version and name: Manjaro Linux 21.2.4, but I don't think it makes a difference for this.

Sort labels alphabetically.

Current behavior

Labels are ordered according to their creation date. This seems a bit awkward to me when looking for a particular tag.

Expected behavior

The labels section should have a drop-down menu from which the labels can be sorted by "Creation date" "Counter" and "Alphabetically".

Captura de pantalla de 2021-10-24 09-23-30

Steps to reproduce

  1. Open Boostnote.
  2. Try to order labels in alphabetically order.

Environment

  • Boost Note.next version:
  • OS version and name:

Multiple tag editing submission

Current behavior

Currently, when adding tags to a note, if I select a tag with the keyboard and press Enter to seeleect itt, it seleects it and closes the selection list.

Expected behavior

Select and add the tag, as it currently does, but afterwards, keep the tag list open and the focus on the tag list search input field, so that another tag can easily be searched and added.

Alternatively, leave it exactly this way but add a sepeerate, say Ctrl+Enter for the submit and add another tag functionality.

Steps to reproduce

Add multiple tags to a note.

Environment

Linux Desktop (but I'm sure it would be useful for other environments as well. Like Web.)

Desktop/Web

  • Boost Note.next local version: 0.21.1
  • OS version and name: Manjaro Linux 21.1.0

Handle malformed note data

Current behavior

图片

Expected behavior

all my notes could be loaded

Steps to reproduce

1.the version is newst
2. its all ok,before i clike the view--reload
3. i reinstall ,restart pc ,can not fix it

Environment

Desktop/Web

pc win10 64bit
version newst

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.