An open-source Notion UI built with Vue 3

Last update: Aug 17, 2022

🧴 Lotion

An open-source Notion UI built with Vue 3.

Try demo


We will be talking about Lotion and the Notion UI during CityJS Singapore's pre-conference meetup on 27th July!

Features

  • Block-based editor
  • Drag to reorder blocks
  • Basic Markdown-parsing including bold, italic, headings and divider
  • Type '/' for command menu and shortcuts

Getting Started

1. Clone this repository, go to the root directory and install packages

git clone https://github.com/dashibase/lotion
cd lotion
npm i

2. Run dev

npm run dev

You should see what looks like the screenshot above.

3. Contribute!

Lotion is quite limited for now but we hope it serves as a good starting point for other folks looking to build their own editors.

We would love to make Lotion more extensible and welcome any suggestions or contributions!

Acknowledgements

This was made much easier with the following libraries and frameworks, thank you!

GitHub

https://github.com/Dashibase/lotion
Comments
  • 1. Set block's unique id for comment and history

    Hi, Thanks for your great open-source project 😍. I have a challenge with this editor, maybe you can help me.

    I want to set and save the block's unique id in DB for some features like comments or the block's history. Do you have any idea about that? Notice that text or structure changes shouldn't change id.

    Thanks.

    Reviewed by faridfr at 2022-07-29 13:20
  • 2. Component search improvements

    Typing /heading shows two variants: Heading 1 and Heading 2.

    However, typing /h1 to quickly access Heading 1 doesn't work. The same is for /h2.

    Additionally, if I insert spaces, just like /heading 1, the search ignores the space, tries to look for heading1, and says that nothing was found. And backspacing three times after that leaves /headi in the search box instead of /headin.

    Reviewed by ivteplo at 2022-07-27 17:47
  • 3. Carret is always put at the end of text after transforming block using slash syntax

    To reproduce:

    1. Convert block to any other type of content block using slash syntax
    2. Caret will be put at the end of text even if slash was somewhere in the middle

    Probably the reason for this: Lotion.vue::setBlockType() calls moveToEnd() even if transformation was invoked with slash syntax. There is a call before that in Block.vue::clearSearch() to set carret at right position.

    Reviewed by PJerkovic at 2022-08-02 01:40
  • 4. fix: make bold and italic tags persist after block conversion (text type)

    Currently, converting between text type blocks (text <-> quote / text->text / quote->quote) results in loss of bold and italic tags

    • Modified Lotion.vue::setBlockType to use html content instead of text content when target block is text/quote (solves conversion with no search)
    • Modified Block.vue::clearSearch to take into account html content - now if type of block before conversion is text/quote, it will store the html content in addition to the text content, and if the type of block after conversion is text/quote, it will use the stored html content. (solves conversion via search)
    video

    keeptags-before keeptags-after

    Tags are still lost when converting to heading types, could potentially find a workaround with the current implementation but @holazz's refactor in #39 will potentially make it much easier (I think we would be able to use the same logic as text blocks)

    Reviewed by vvidday at 2022-08-04 02:37
  • 5. fix: block conversion issues

    Proposed solutions for #27 and #29

    For #27, Block.vue::clearSearch previously wouldn't trigger on a single slash since the slash was not added to searchTerm. Also changed <= 1 to <1 to support single character search terms (e.g. /h)

    For #29 please see issue

    27

    27-before 27-after

    29

    29-before 29-after

    Reviewed by vvidday at 2022-08-02 10:39
  • 6. Deleting all blocks leaves the editor in a state where new blocks can't be added

    I was playing around with the demo at https://lotion.dashibase.com/ and noticed that if I delete all the blocks (I started at the bottom and moved upwards, leaving just the main heading remaining), I get into a state where I can't seem to add any new blocks. At this point, I don't see a tooltip to add a new block and the '/' shortcut doesn't popup the menu to convert a block to a particular type.

    Reviewed by allanw at 2022-08-01 08:48
  • 7. fix: drag & drop issues

    Proposed solution for #10

    Using handle property for draggable

    Associating handle with BlockMenu component ensures that blocks can be dragged only when the user is dragging the BlockMenu icon, instead of anywhere on the div. Mirrors actual notion behaviour. Example

    Removing default behaviour for drop event

    Issue where dropping a block inside itself would result in unwanted behaviour, seems to be caused by the HTML drop event on the editor. Preventing the default drop event inside the editor seems to fix the issue.

    Side note: The following solution seems more clear, but it throws a typescript error for some reason. Current code does the same (Reference)

        editorProps: { 
          handleDOMEvents : {
            drop: (view, event) => {event.preventDefault()}
          }
        }
    

    Comparisons:

    Handle

    draghandle-before draghandle-after

    Drop

    drop-before drop-after

    Reviewed by vvidday at 2022-07-31 14:10
  • 8. Enhance markdown parser

    Before:

    https://user-images.githubusercontent.com/23100055/182024052-c7a31e5d-520f-46b0-afe6-2b443aea1d2e.mov

    After:

    https://user-images.githubusercontent.com/23100055/182024060-cc95d4ee-6d26-4c5b-88ec-f0ef8cc24851.mov

    Reviewed by holazz at 2022-07-31 11:27
  • 9. Clear search only if there is searchTerm

    There is no need to send event to clear search term if there is none.

    This also fixes bug that removed last character when transforming from Text into Heading. Should I also create issue for this?

    Reviewed by PJerkovic at 2022-07-30 14:11
  • 10. Block menu improvements

    This pull request:

    • Makes search more convenient by using fuzzy search instead of String#startsWith
    • Fixes a bug of search term not being set back to an empty string when user presses Esc or clicks outside the block menu
    • Fixes spaces being ignored when a user inputs a search query. Notion doesn't ignore spaces (you can type /heading 1 and it will work)
    Reviewed by ivteplo at 2022-07-28 19:51
  • 11. add unique id to blocks

    Description

    This PR addresses #12

    • default blocks to have id
    • new blocks to have id
    • In the HTML DOM, blocks also have the id of block-${block.id}

    Screenshots

    image
    Reviewed by lyqht at 2022-08-04 11:51
  • 12. test: adding tests (playwright)

    Setup + basic tests using playwright

    Currently implements the following (E2E) tests: | Use Case / Feature | Expected Behaviour | |-------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Basic typing / editing | Text should update as user types
    Text should be formatted appropriately when user inputs bold/italics | | Converting between types | Blocks should be converted appropriately when user selects a type from the menu
    Blocks should be converted appropriately when user initiates conversion by searching/typing
    Caret position should be maintained after conversion if initiated from search | | Creating new lines / Breaking lines | Pressing 'Enter' at the end of a block should create a new line
    Pressing 'Enter' in the middle of a block should create a new line containing the text after the caret position | | Merging two lines | Pressing 'Backspace' at the beginning of a text block should merge the current block's content with the previous block
    Pressing 'Backspace' at the beginning of a non-text block should convert the current block to a text block |

    I also included an example using playwright's component testing (c9a30f1fbde21fe0b65abc9c014f1a486ba78891 and 89329ed9fb307f0edd9c6209e9a18c3824f9aeea), which will be very useful since it allows us to mount components with custom props. (eg if we want to test custom inputs like bold/italic bold/headings etc we can mount the component with these inputs instead of having to create them in an end to end test)

    One 'downside' of playwright compared to cypress is that we can't (to my knowledge) access instance methods (for example directly calling Lotion.vue::getCaretPos()), but we can still test the correctness of the methods outcome e.g. by initiating a change in block type via the menu.

    Also, I'm not sure regarding CI (I'm not familiar with AWS), currently npx playwright test is configured to start the local server with npm run dev and test on that url - might need some changes for CI.

    Reviewed by vvidday at 2022-08-13 08:23
  • 13. test: adding tests

    As more features are being added, it would be great if we can set up automated tests to verify the correctness of any changes and ensure new changes don't break current features.

    This is a very rough draft using cypress which includes some basic tests for Lotion::split, Lotion::merge, Block::getCaretPos and Block::getCaretPosWithoutTags.

    Would greatly appreciate any input/suggestions on the approach/organization etc or if anyone would like to suggest another testing library. Wanted to check on this approach before I proceed to write more tests covering the existing features.

    Example run

    test-draft

    Reviewed by vvidday at 2022-08-07 14:49
  • 14. fix: make bold and italic tags persist after block conversion (text type)

    Currently, converting between text type blocks (text <-> quote / text->text / quote->quote) results in loss of bold and italic tags

    • Modified Lotion.vue::setBlockType to use html content instead of text content when target block is text/quote (solves conversion with no search)
    • Modified Block.vue::clearSearch to take into account html content - now if type of block before conversion is text/quote, it will store the html content in addition to the text content, and if the type of block after conversion is text/quote, it will use the stored html content. (solves conversion via search)
    video

    keeptags-before keeptags-after

    Tags are still lost when converting to heading types, could potentially find a workaround with the current implementation but @holazz's refactor in #39 will potentially make it much easier (I think we would be able to use the same logic as text blocks)

    redo of #42 with correct commit history

    Reviewed by vvidday at 2022-08-05 02:48
  • 15. Support for italic bold / nested tags

    Currently, working with text that is both bold and italic causes a few functions to break

    • Arrow key navigation doesn't work if italic bold text is at front/back of block
    • Merge doesn't work if italic bold text is at the front of the block that is being merged
    • Split doesn't work if initiated from within the italic bold text
    • Conversion via search doesn't delete search term if initiated from within italic bold text

    Issues are mainly due to the tags being nested, since most functions currently only work for one layer of nested tags

    Video

    italicbold-issue

    Fix would require a decent amount of refactoring to support nested tags

    Reviewed by vvidday at 2022-08-04 02:59
  • 16. Caret moves down when trying to add multiple blocks

    Description

    In Notion, when you click on the "+" button next to the block, you will see a context menu to choose the custom block that you want to add to your document, so there isn't a behavior of repeatedly clicking on the "+" button.

    However, in Lotion, right now, when we click on the "+" button repeatedly, the caret is immediately moved to the new block. This results the user to leave where they are previously. While this logic does seem to follow how Notion implements it, it is not ideal for users that just want to add multiple placeholder blocks.

    Screenshots

    scroll_position_shifts_down_when_adding_items

    Reviewed by lyqht at 2022-08-03 12:38
  • 17. Add issue & PR templates to GitHub repository

    Seeing how Dashibase is getting many issue report & PRs, it will be great to include som templates so there's a standard for new contributors to follow when creating an issue for bugs/ feature requests, and when they create PRs for them.

    Examples of GitHub templates can be found here and here

    Reviewed by lyqht at 2022-08-03 12:24
Piman is an open-source accessibility UI framework create by Blueplanet Inc.

Piman UI framework Piman is an open-source accessibility UI framework create by Blueplanet Inc. ?? Getting started Install npm install @yasai/piman Vu

Jul 17, 2022
🎉 A high quality component library built on Vue.js 2.0

dao-style A high quality component library built on Vue.js. English | 简体中文 Docs latest Who's using dao-style DaoCloud DaoVoice If you are also using d

Jul 10, 2022
Vue components built for Nuxt3|Vue3 powered by Windi CSS|tailwindcss
Vue components built for Nuxt3|Vue3 powered by Windi CSS|tailwindcss

Vue components built for Vue3 powered by Windi CSS or tailwindcss ?? Features ?? All components and classes include dark-mode support ?? Programmatic

Aug 11, 2022
A library of UI widgets built using Vue.js and TailwindCSS.
A library of UI widgets built using Vue.js and TailwindCSS.

This library contains a set of UI widgets / components. They range from something as simple as a button, to as complex as a fully-featured Markdown-based text editor. Originally, these components were exclusively part of Lumeno's codebase, however they have since been extracted, extended and made available for open-source use.

Aug 9, 2022
A library of UI components built using Vue.js and TailwindCSS
A library of UI components built using Vue.js and TailwindCSS

Varnish This library contains a set of UI components. They range from something as simple as a button, to as complex as a fully-featured, Markdown-bas

Aug 16, 2022
Vue3 ready components library built with love and care designed to integrate beautifully with Bulma CSS

Vue3-ui Vue3 ready components library built with love and care designed to integrate beautifully with Bulma CSS Documentation The full documentation f

Aug 13, 2022
🎩 Vue Demi is a developing utility allows you to write Universal Vue Libraries for Vue 2 & 3
🎩 Vue Demi  is a developing utility allows you to write Universal Vue Libraries for Vue 2 & 3

?? Vue Demi (half in French) is a developing utility allows you to write Universal Vue Libraries for Vue 2 & 3

Aug 14, 2022
🌟Shards Vue is a free, beautiful and modern Vue.js UI kit based on Shards.
🌟Shards Vue is a free, beautiful and modern Vue.js UI kit based on Shards.

Shards Vue is a free, beautiful and modern Vue.js UI kit based on Shards. Documentation & Demo • Official Page Getting Started Getting started with Sh

Jun 2, 2022
Vue.js components implementation of Fundamental Library Styles guidelines. The library is aiming to provide a Vue.js implementation of the components designed in Fundamental Library Styles.

Fundamental Vue Description The fundamental-vue library is a set of Vue.js components built using Fundamental Library Styles. Fundamental Library for

Jul 5, 2022
An emerging UI framework for Vue.js & Vue 3 with only the bright side. ☀️

Wave UI An emerging UI framework for Vue.js & Vue 3 with only the bright side. ☀️ Demo & Documentation antoniandre.github.io/wave-ui install npm i wav

Aug 10, 2022
MADE Vue - A library of Vue 3 UI Components
 MADE Vue - A library of Vue 3 UI Components

MADE Vue A library of Vue 3 UI Components. Support MADE Vue ♥ Projects like MADE Vue are built and maintained in spare time. If you find this project

May 1, 2022
🌟Shards Vue is a free, beautiful and modern Vue.js UI kit based on Shards.
🌟Shards Vue is a free, beautiful and modern Vue.js UI kit based on Shards.

Shards Vue is a free, beautiful and modern Vue.js UI kit based on Shards. Documentation & Demo • Official Page Getting Started Getting started with Sh

Jun 2, 2022
Material design for Vue.js
Material design for Vue.js

Material Design for Vue.js Vue Material is Simple, lightweight and built exactly according to the Google Material Design specs Build well-designed app

Aug 17, 2022
🐉 Material Component Framework for Vue
🐉 Material Component Framework for Vue

Supporting Vuetify Vuetify is a MIT licensed project that is developed and maintained full-time by John Leider and Heather Leider; with support from t

Aug 18, 2022
Lightweight UI components for Vue.js based on Bulma
Lightweight UI components for Vue.js based on Bulma

Buefy is a lightweight library of responsive UI components for Vue.js based on Bulma framework and design. Features Keep your current Bulma theme / va

Aug 16, 2022
A Vue.js 2.0 UI Toolkit for Web
A Vue.js 2.0 UI Toolkit for Web

A Vue.js 2.0 UI Toolkit for Web. Element will stay with Vue 2.x For Vue 3.0, we recommend using Element Plus from the same team Links Homepage and doc

Aug 13, 2022
Translate Bulma css api to vue components

vue-bulma-components The goal of this library is to use the bulma class syntax as components and props. 3kb minified Demo and try the live demo too :)

Apr 26, 2022
BootstrapVue provides one of the most comprehensive implementations of Bootstrap v4 for Vue.js. With extensive and automated WAI-ARIA accessibility markup.
BootstrapVue provides one of the most comprehensive implementations of Bootstrap v4 for Vue.js. With extensive and automated WAI-ARIA accessibility markup.

With more than 85 components, over 45 available plugins, several directives, and 1000+ icons, BootstrapVue provides one of the most comprehensive impl

Aug 15, 2022