Skip to content

safdar-azeem/vue-notion-editor

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 

Repository files navigation

Vue Notion Editor

A powerful, feature-rich Vue 3 rich text editor component inspired by Notion, It delivers a complete editing experience with slash commands, context menus, drag-and-drop support, advanced formatting, enhanced tables, and image upload capabilities.

Features

  • Rich Text Formatting: Bold, italic, underline, strikethrough, code, and color formatting
  • Advanced Block Types: Headings, blockquotes, code blocks, lists, and task lists
  • Table Support: Create, edit, and manipulate tables with advanced cell & row operations, including interactive drag-to-add/remove rows.
  • Image Handling: Drag-and-drop image uploads with customizable upload functions.
  • File Handler: Smart paste handling for images and rich content
  • Image Resizing & Alignment: Smoothly resize images with interactive handles and align them left, right, or center.
  • Drag & Drop: Reorder content blocks with visual drag handles
  • Slash Commands: Quick content insertion with "/" command menu
  • Link Management: Smart link insertion and editing with bubble menu
  • Text Alignment: Left, center, right, and justify alignment options
  • Bubble Menu: Context-sensitive formatting toolbar
  • Undo/Redo: Full history management with keyboard shortcuts
  • Task Lists: Interactive checkboxes and task management
  • Customizable: Flexible styling with CSS variables and custom themes
  • TypeScript Support: Fully typed for enhanced developer experience

Demo

Live Demo

Installation

# npm
npm install v-notion-editor

# yarn
yarn add v-notion-editor

# pnpm
pnpm add v-notion-editor

Basic Usage

<script setup>
import { ref } from 'vue'
import { NotionEditor, NotionToolbar } from 'v-notion-editor'
import 'v-notion-editor/style.css'

const content = ref('<p>Start writing your content here...</p>')

const handleImageUpload = async (files) => {
   // Implement your image upload logic
   const fileToBase64 = (file) => {
      return new Promise((resolve, reject) => {
         const reader = new FileReader()
         reader.onload = () => resolve(reader.result)
         reader.onerror = reject
         reader.readAsDataURL(file)
      })
   }

   return await Promise.all(files.map(fileToBase64))
}
</script>

<template>
   <div>
      <NotionToolbar />
      <NotionEditor
         v-model="content"
         :image-upload-options="{
            onUpload: handleImageUpload,
            maxFileSize: 10 * 1024 * 1024, // 10MB
            quality: 0.85,
         }" />
   </div>
</template>

📁 Importing Styles

You must import the CSS styles:

import 'v-notion-editor/style.css'

Components

NotionEditor

The main editor component that provides the rich text editing interface.

Props

Prop Type Default Description
modelValue string '' The HTML content of the editor
imageUploadOptions ImageUploadOptions {} Configuration for image upload handling

Events

Event Parameters Description
update:modelValue value: string Emitted when content changes
onFocus - Emitted when editor gains focus
onBlur - Emitted when editor loses focus

Image Upload Options

interface ImageUploadOptions {
   onUpload?: (files: File[]) => Promise<string[]>
   maxFileSize?: number // in bytes, default: 10MB
   quality?: number // 0-1, default: 0.85
   allowedTypes?: string[] // default: ['image/png', 'image/jpeg', 'image/gif', 'image/webp']
   onOpenFileDialog?: (multiple: boolean) => Promise<string[] | File[]> // custom file picker
}

NotionToolbar

A comprehensive toolbar component with formatting options.

Props

Prop Type Default Description
visibleToolbars string[] All tools Array of toolbar items to display

Available Toolbar Items

headings, bold, italic, underline, strike, code, color, link, bulletList, orderedList, taskList, blockquote, table, align, image, divider, undo, redo

Usage Examples

1. Custom Toolbar Configuration

<script setup>
import { NotionEditor, NotionToolbar } from 'v-notion-editor'

const customToolbars = ['headings', 'bold', 'italic', 'bulletList', 'orderedList', 'link', 'image']
</script>

<template>
   <div>
      <NotionToolbar :visible-toolbars="customToolbars" />
      <NotionEditor v-model="content" />
   </div>
</template>

2. Editor Instance Access

<script setup>
import { ref, onMounted } from 'vue'
import { NotionEditor } from 'v-notion-editor'

const editorRef = ref(null)

const insertCustomContent = () => {
   const editor = editorRef.value?.editor
   if (editor) {
      editor.chain().focus().insertContent('<p>Custom inserted content!</p>').run()
   }
}

const getEditorContent = () => {
   const editor = editorRef.value?.editor
   if (editor) {
      console.log('HTML:', editor.getHTML())
      console.log('JSON:', editor.getJSON())
      console.log('Text:', editor.getText())
   }
}
</script>

<template>
   <div>
      <button @click="insertCustomContent">Insert Content</button>
      <button @click="getEditorContent">Get Content</button>

      <NotionEditor ref="editorRef" v-model="content" />
   </div>
</template>

3. Read-Only Content Display

To display editor content with the same styling in read-only mode, wrap your content with the v-notion-editor class:

<script setup>
import 'v-notion-editor/style.css'

const savedContent = ref(`
  <h1>My Document Title</h1>
  <p>This content will have the same styling as in the editor.</p>
  <blockquote>Important note with proper formatting</blockquote>
`)
</script>

<template>
   <!-- Read-only content with editor styling -->
   <div class="v-notion-editor">
      <div v-html="savedContent" class="read-mode" />
   </div>
</template>

Keyboard Shortcuts

Shortcut Action
Ctrl/Cmd + B Bold
Ctrl/Cmd + I Italic
Ctrl/Cmd + U Underline
Ctrl/Cmd + Shift + S Strikethrough
Ctrl/Cmd + E Code
Ctrl/Cmd + K Link
Ctrl/Cmd + Z Undo
Ctrl/Cmd + Shift + Z Redo
Ctrl/Cmd + Shift + L Bullet List
Ctrl/Cmd + Shift + O Ordered List
Ctrl/Cmd + Enter Hard Break
/ Slash command menu

Slash Commands

Type / to access quick content insertion:

Styling

Customize the editor appearance using CSS variables:

:root {
   /* Editor Base */
   --editor-bg: #ffffff;
   --editor-color: #000000;
   --editor-primary: #dbeafe;
   --editor-primary-fg: #1d4ed8;

   --editor-secondary: #f7f6f4;
   --editor-secondary-dark: #c2c2bd;
   --editor-border-color: #ececea;
   --editor-btn-size: 1.7em;
   --editor-btn-icon-size: calc(var(--editor-btn-size) - 0.7em);
   --editor-border-radius: 0.5rem;
   --editor-shadow: 0 4px 80px -4px rgba(0, 0, 0, 0.088);
   --spacing-btn-size: var(--editor-btn-size);
   --radius: var(--editor-border-radius);

   /* Content Colors */
   --editor-content-color-default: #37352f;
   --editor-content-color-gray: #989898;
   --editor-content-color-orange: #ea580c;
   --editor-content-color-yellow: #ecb802;
   --editor-content-color-green: #16a34a;
   --editor-content-color-blue: #2563eb;
   --editor-content-color-purple: #9333ea;
   --editor-content-color-pink: #f64f9f;
   --editor-content-color-red: #e0383e;
   --editor-content-color-white: #ffffff;
   --editor-content-color-heading: #292929;
   --editor-content-color-text: #37352f;
   --editor-content-color-text-secondary: #4f4f4f;
   --editor-content-color-code: #e3342f;
   --editor-content-color-code-block: #272727;
   --editor-content-color-mark: #151413;
   --editor-content-color-primary: #007bff;
   --editor-content-color-primary-dark: #007bff;
   --editor-content-color-primary-dark-light: #007bff;
   --editor-content-color-border: #c8c8c6;
   --editor-content-color-border-dark: #d0d0ce;

   /* Content Background Colors */
   --editor-content-bg-default: rgba(55, 53, 47, 0.12);
   --editor-content-bg-gray: #d5d7d8;
   --editor-content-bg-yellow: #ffebb4;
   --editor-content-bg-yellow-light: #f3f1eb;
   --editor-content-bg-green: #cbecbf;
   --editor-content-bg-blue: #b4d6ff;
   --editor-content-bg-purple: rgba(105, 64, 165, 0.4);
   --editor-content-bg-pink: #fecbe2;
   --editor-content-bg-red: #ff9898;
   --editor-content-bg-black: #000000;
   --editor-content-bg-secondary: #f7f6f4;
   --editor-content-bg-mark: #fef08a;
   --editor-content-bg-primary: #007bff;
   --editor-content-bg-primary-dark: #0a77ec;
   --editor-content-bg-primary-light: #eff5fd;
   --editor-content-bg-primary-fg: #ffffff;

   /* Headings */
   --size-font-h1: 2.15em;
   --size-font-h2: 1.65em;
   --size-font-h3: 1.4em;
   --size-font-h4: 1.35em;
   --size-font-h5: 1.3em;
   --size-font-h6: 1.125em;
   --size-line-height-heading: 1.25;

   /* Table */
   --size-font-table: 1.1em;
   --size-padding-table: 8px 12px;
   --size-padding-table-cell: 0.5em 0.75em;
   --size-table-cell-min-width: 100px;
   --z-index-selected-cell: 2;

   /* Code */
   --size-font-code: 0.875em;

   /* Fonts */
   --font-family-base: 'Roboto', Arial, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
   --font-weight-medium: 500;
   --font-weight-semibold: 600;
   --font-weight-bold: 700;
   --size-font-base: 15px;
   --size-font-sm: 0.875em;
   --size-font-xs: 0.75em;

   /* Spacing / Padding / Margin  */
   --size-spacing-sm: 0.25rem;
   --size-spacing-md: 0.5rem;
   --size-spacing-lg: 0.75rem;
   --size-spacing-xl: 1rem;
   --size-spacing-2xl: 1.5rem;
   --size-spacing-3xl: 2rem;

   --size-padding-sm: 0.125rem;
   --size-padding-md: 0.375rem;
   --size-padding-lg: 0.75rem;
   --size-padding-xl: 1rem;

   --size-margin-sm: 0.2em;
   --size-margin-md: 0.58em;
   --size-margin-lg: 0.88em;
   --size-margin-xl: 1.18em;
   --size-margin-2xl: 1.45em;

   /* Borders  */
   --size-border-radius-sm: 0.25rem;
   --size-border-radius-md: 0.375rem;
   --size-border-radius-lg: 0.5rem;
   --size-border-radius-xl: 1px;
   --size-border-width-sm: 0.95px;
   --size-border-width-md: 2px;
   --size-border-width-lg: 3px;

   /* Line Heights */
   --size-line-height-base: 1.6;
   --size-line-height-list: 1.6;
   --size-line-height-list-marker: 1.4;

   /* Misc Sizes */
   --size-cursor-width: 20px;
   --size-resize-handle: 4px;
   --size-drag-handle-width: 1.03rem;
   --size-drag-handle-height: 1.4rem;
}

Reporting Bugs or Requesting Features

If you encounter a bug, miss a feature, or want to suggest an enhancement: Open an issue on GitHub: issues Provide a clear description, steps to reproduce, and screenshots if possible. For feature requests, describe your use case and expected behavior.

License

MIT

Author

safdar-azeem