From eb87d27fdd6f2a661f031dea0a99394e2fbd156d Mon Sep 17 00:00:00 2001 From: m8n-matandessaur Date: Wed, 29 Oct 2025 14:50:07 -0400 Subject: [PATCH] feature/add-betterquill-plugin --- plugins/Better-Quill-Plugin/.babelrc | 8 + plugins/Better-Quill-Plugin/.gitignore | 30 + plugins/Better-Quill-Plugin/LICENSE | 15 + plugins/Better-Quill-Plugin/README.md | 209 ++++++ plugins/Better-Quill-Plugin/package.json | 55 ++ plugins/Better-Quill-Plugin/src/plugin.jsx | 677 ++++++++++++++++++ plugins/Better-Quill-Plugin/webpack.config.js | 48 ++ 7 files changed, 1042 insertions(+) create mode 100644 plugins/Better-Quill-Plugin/.babelrc create mode 100644 plugins/Better-Quill-Plugin/.gitignore create mode 100644 plugins/Better-Quill-Plugin/LICENSE create mode 100644 plugins/Better-Quill-Plugin/README.md create mode 100644 plugins/Better-Quill-Plugin/package.json create mode 100644 plugins/Better-Quill-Plugin/src/plugin.jsx create mode 100644 plugins/Better-Quill-Plugin/webpack.config.js diff --git a/plugins/Better-Quill-Plugin/.babelrc b/plugins/Better-Quill-Plugin/.babelrc new file mode 100644 index 00000000000..d8cf250dc2b --- /dev/null +++ b/plugins/Better-Quill-Plugin/.babelrc @@ -0,0 +1,8 @@ +{ + "presets": ["@babel/preset-react"], + "env": { + "development": { + "presets": [["@babel/preset-react", { "development": true }]] + } + } +} diff --git a/plugins/Better-Quill-Plugin/.gitignore b/plugins/Better-Quill-Plugin/.gitignore new file mode 100644 index 00000000000..71941fa40df --- /dev/null +++ b/plugins/Better-Quill-Plugin/.gitignore @@ -0,0 +1,30 @@ +# Dependencies +node_modules/ +package-lock.json +yarn.lock + +# Build output +dist/ + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Environment +.env +.env.local +.env.development.local +.env.test.local +.env.production.local diff --git a/plugins/Better-Quill-Plugin/LICENSE b/plugins/Better-Quill-Plugin/LICENSE new file mode 100644 index 00000000000..1257f33ca76 --- /dev/null +++ b/plugins/Better-Quill-Plugin/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2025 Matan Dessaur + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/plugins/Better-Quill-Plugin/README.md b/plugins/Better-Quill-Plugin/README.md new file mode 100644 index 00000000000..8e7b2967292 --- /dev/null +++ b/plugins/Better-Quill-Plugin/README.md @@ -0,0 +1,209 @@ +# BetterQuill - Builder.io Plugin + +> A superior rich text editing experience for Builder.io, surpassing the default Quill integration with modern features and professional UI. + +**BetterQuill** is a comprehensive Quill.js-based rich text editor plugin for Builder.io that provides everything the default plugin offers and more — with advanced features including professional table management, dark/light themes, fullscreen editing, and a powerful CodeMirror-powered HTML code editor. + +## Why BetterQuill? + +The default Quill plugin in Builder.io is basic. **BetterQuill** takes it to the next level with: + +✅ **Professional UI/UX** - Modern, polished interface with smooth animations +✅ **Dark & Light Modes** - Perfect contrast with `#191919` dark theme +✅ **Advanced Tables** - Full table editor with merge/split cells (powered by quill-better-table) +✅ **Code View** - Professional HTML editor with CodeMirror 6, syntax highlighting, and auto-formatting +✅ **Fullscreen Mode** - Distraction-free editing with centered 800px content area +✅ **Better Typography** - Improved spacing, line heights, and readability +✅ **Modern Controls** - Sleek buttons with hover effects and visual feedback + +## Features + +### 📝 Text Formatting +- **Headers**: H1-H6 heading styles with proper contrast +- **Text Styles**: Bold, Italic, Underline, Strikethrough +- **Colors**: Full color picker for text and background highlighting +- **Alignment**: Left, Center, Right, Justify +- **Superscript/Subscript**: Scientific notation support + +### 📋 Content Elements +- **Lists**: Ordered, Bullet, and Checklist +- **Indentation**: Increase/Decrease indent levels +- **Blockquotes**: Quote formatting with accent border +- **Code Blocks**: Inline code with modern styling +- **Links**: Hyperlink management +- **Images**: Insert and manage images +- **Videos**: Embed video content + +### 🎨 Advanced Features + +#### Tables (quill-better-table) +Full-featured table editor with context menu: +- Insert/delete rows and columns +- Merge and split cells +- Right-click context menu for all operations +- Clean, professional table styling + +#### Dark & Light Modes +- **Dark Mode**: Professional `#191919` background with white text and perfect contrast +- **Light Mode**: Clean white background with dark text +- Consistent theming across all UI elements +- Smooth theme transitions + +#### Fullscreen Mode +- Distraction-free editing experience +- Centered content area (800px max-width) +- Full viewport height with `100svh` +- Maintains theme and functionality + +#### Code View (CodeMirror 6) +Professional HTML source editor: +- **Syntax Highlighting**: Full HTML syntax coloring +- **Auto-Formatting**: Beautiful code with proper indentation (js-beautify) +- **Line Numbers**: Professional gutter display +- **Editable**: Make changes directly in source code +- **Theme Support**: One Dark theme in dark mode + +### 🎨 Modern UI Design +- Smooth transitions and hover effects +- Clean button design with visual feedback +- Custom styled scrollbars +- Professional spacing and typography +- Visible borders with `#393939` in dark mode +- Centered toolbar icons with flexbox +- Glass morphism effects on control bar + +## Installation + +1. **Install Dependencies** + +```bash +npm install +``` + +2. **Start Development Server** + +```bash +npm start +``` + +The plugin will be available at `http://localhost:1268/plugin.system.js` + +3. **Build for Production** + +```bash +npm run build +``` + +The production build will be in the `dist` folder. + +## Adding the Plugin to Builder.io + +### Local Development + +1. Start the development server: `npm start` +2. Go to [Builder.io Account Settings](https://builder.io/account/settings) +3. Click the pencil icon next to "Plugins" +4. Add the local URL: `http://localhost:1268/plugin.system.js` +5. Click Save + +**Note for Chrome Users**: When developing locally on `http://localhost`, you need to allow insecure content: +- Click the shield icon in the address bar +- Select "Load unsafe scripts" +- The page will reload + +### Using the Plugin + +1. Go to **Models** in Builder.io +2. Select or create a model +3. Click **+ New Field** +4. In the **Type** dropdown, scroll down and select **RichText** +5. The rich text editor will appear with all features + +## Plugin Features Guide + +### 🌙 Dark Mode +Click the **🌙 Dark** button to toggle between light and dark themes. This provides better visibility in different lighting conditions. + +### ⛶ Fullscreen Mode +Click the **⛶ Fullscreen** button to expand the editor to full screen for focused, distraction-free editing. + +### Code View +Click the ** Code** button to toggle between: +- **Visual Editor**: WYSIWYG editing experience +- **HTML Source**: Direct HTML code editing for advanced users + +### ⊞ Tables +Click the **⊞** button in the toolbar to insert a 3x3 table. Right-click on any cell to: +- Insert rows above/below +- Insert columns left/right +- Merge/unmerge cells +- Delete rows/columns/table + +## Technical Details + +### Core Dependencies +- **Quill 2.0+**: Modern WYSIWYG editor +- **quill-better-table**: Advanced table management with merge/split +- **CodeMirror 6**: Professional code editor for HTML view +- **js-beautify**: HTML auto-formatting and indentation +- **@builder.io/react**: Builder.io React SDK +- **@emotion/core**: CSS-in-JS styling + +### Key Technologies +- **React**: Component-based architecture +- **Webpack**: Module bundling and dev server +- **System.js**: Dynamic module loading for Builder.io +- **CSS-in-JS**: Dynamic theming with emotion + +### Browser Compatibility +- Chrome/Edge (latest) +- Firefox (latest) +- Safari (latest) + +## Customization + +BetterQuill is designed to be customizable. Edit `src/plugin.jsx` to modify: + +- **Toolbar Options**: Customize the `toolbarOptions` array +- **Theme Colors**: Adjust dark/light mode colors in `containerStyles` +- **Editor Behavior**: Modify Quill modules and settings +- **UI Styling**: Update CSS-in-JS for buttons, borders, and spacing + +## Troubleshooting + +### Plugin Not Loading +1. Verify the development server is running on port 1268 +2. Check browser console for errors +3. Ensure you've allowed insecure scripts in Chrome (see installation notes) +4. Confirm Builder.io plugin URL is correct + +### Code View Not Working +1. Ensure all CodeMirror dependencies are installed +2. Check that js-beautify is properly installed +3. Clear browser cache and rebuild + +### Styling Issues +1. Clear your browser cache +2. Rebuild the plugin: `npm run build` +3. Refresh Builder.io +4. Check for CSS conflicts in browser DevTools + +## Author + +**Matan Dessaur** (M8N-MatanDessaur) +📧 [hello@matandessaur.me](mailto:hello@matandessaur.me) + +## License + +ISC License - Free to use and modify + +## Support & Contributing + +For issues, questions, or contributions: +- 📧 Email: [hello@matandessaur.me](mailto:hello@matandessaur.me) +- 📚 [Builder.io Documentation](https://www.builder.io/c/docs) +- 📖 [Quill.js Documentation](https://quilljs.com/docs/) + +--- + +**BetterQuill** - Because your content deserves better. 🚀 diff --git a/plugins/Better-Quill-Plugin/package.json b/plugins/Better-Quill-Plugin/package.json new file mode 100644 index 00000000000..901ea580509 --- /dev/null +++ b/plugins/Better-Quill-Plugin/package.json @@ -0,0 +1,55 @@ +{ + "name": "betterquill-builderio", + "version": "1.0.0", + "description": "BetterQuill - A superior Builder.io plugin with advanced features: tables, dark mode, fullscreen, and CodeMirror-powered code view", + "keywords": [ + "builder.io", + "plugin", + "rich-text", + "quill", + "wysiwyg", + "editor", + "betterquill", + "table", + "dark-mode", + "codemirror", + "fullscreen" + ], + "entry": "plugin", + "output": "plugin.system.js", + "main": "dist/plugin.system.js", + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "webpack --mode production", + "start": "webpack-dev-server --mode development" + }, + "author": "Matan Dessaur (M8N-MatanDessaur)", + "license": "ISC", + "devDependencies": { + "@babel/preset-react": "^7.18.6", + "@builder.io/app-context": "^1.0.0", + "@builder.io/react": "^2.0.13", + "@emotion/core": "^11.0.0", + "babel-loader": "^8.2.5", + "css-loader": "^6.7.1", + "style-loader": "^3.3.1", + "webpack": "^5.74.0", + "webpack-cli": "^4.10.0", + "webpack-dev-server": "^4.10.0" + }, + "dependencies": { + "quill": "^2.0.2", + "quill-better-table": "^1.2.10", + "codemirror": "^6.0.1", + "@codemirror/view": "^6.23.0", + "@codemirror/state": "^6.4.0", + "@codemirror/lang-html": "^6.4.7", + "@codemirror/theme-one-dark": "^6.1.2", + "@codemirror/commands": "^6.3.3", + "@codemirror/language": "^6.10.0", + "js-beautify": "^1.14.11" + } +} diff --git a/plugins/Better-Quill-Plugin/src/plugin.jsx b/plugins/Better-Quill-Plugin/src/plugin.jsx new file mode 100644 index 00000000000..17420806afc --- /dev/null +++ b/plugins/Better-Quill-Plugin/src/plugin.jsx @@ -0,0 +1,677 @@ +/** @jsx jsx */ +import { jsx, css } from '@emotion/core'; +import { Builder } from '@builder.io/react'; +import React, { useEffect, useRef, useState } from 'react'; +import Quill from 'quill'; +import QuillBetterTable from 'quill-better-table'; +import { html as beautifyHtml } from 'js-beautify'; +import { EditorView, keymap } from '@codemirror/view'; +import { EditorState } from '@codemirror/state'; +import { html as htmlLang } from '@codemirror/lang-html'; +import { oneDark } from '@codemirror/theme-one-dark'; +import { defaultKeymap, indentWithTab } from '@codemirror/commands'; +import { basicSetup } from 'codemirror'; +import 'quill/dist/quill.snow.css'; +import 'quill/dist/quill.bubble.css'; +import 'quill-better-table/dist/quill-better-table.css'; + +// Register the table module +Quill.register('modules/better-table', QuillBetterTable); + +function RichTextEditor(props) { + const editorRef = useRef(null); + const quillRef = useRef(null); + const containerRef = useRef(null); + const codeEditorRef = useRef(null); + const codeMirrorRef = useRef(null); + const [isDarkMode, setIsDarkMode] = useState(true); + const [isFullscreen, setIsFullscreen] = useState(false); + const [isCodeView, setIsCodeView] = useState(false); + + useEffect(() => { + if (!editorRef.current || quillRef.current) return; + + // Comprehensive toolbar configuration + const toolbarOptions = [ + // Text formatting + [{ 'header': [1, 2, 3, 4, 5, 6, false] }], + + // Text styles + ['bold', 'italic', 'underline', 'strike'], + + // Text color and background + [{ 'color': [] }, { 'background': [] }], + + // Superscript/subscript + [{ 'script': 'sub'}, { 'script': 'super' }], + + // Lists and indentation + [{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }], + [{ 'indent': '-1'}, { 'indent': '+1' }], + + // Text alignment + [{ 'align': [] }], + + // Block elements + ['blockquote', 'code-block'], + + // Media + ['link', 'image', 'video'], + + // Clean formatting + ['clean'] + ]; + + // Initialize Quill with all modules + const quill = new Quill(editorRef.current, { + theme: 'snow', + modules: { + toolbar: toolbarOptions, + 'better-table': { + operationMenu: { + items: { + unmergeCells: { + text: 'Unmerge cells' + }, + insertColumnRight: { + text: 'Insert column right' + }, + insertColumnLeft: { + text: 'Insert column left' + }, + insertRowUp: { + text: 'Insert row up' + }, + insertRowDown: { + text: 'Insert row down' + }, + mergeCells: { + text: 'Merge selected cells' + }, + deleteColumn: { + text: 'Delete column' + }, + deleteRow: { + text: 'Delete row' + }, + deleteTable: { + text: 'Delete table' + } + } + } + }, + keyboard: { + bindings: QuillBetterTable.keyboardBindings + } + } + }); + + // Add table button to toolbar + const toolbar = quill.getModule('toolbar'); + toolbar.addHandler('table', function() { + const tableModule = quill.getModule('better-table'); + tableModule.insertTable(3, 3); + }); + + // Set initial content + if (props.value) { + quill.root.innerHTML = props.value; + } + + // Handle content changes + quill.on('text-change', () => { + const html = quill.root.innerHTML; + if (props.onChange) { + props.onChange(html); + } + }); + + quillRef.current = quill; + + // Add custom table button to toolbar + const tableButton = document.createElement('button'); + tableButton.innerHTML = '⊞'; + tableButton.className = 'ql-table'; + tableButton.title = 'Insert Table'; + tableButton.onclick = () => { + const tableModule = quill.getModule('better-table'); + tableModule.insertTable(3, 3); + }; + + const toolbarElement = containerRef.current.querySelector('.ql-toolbar'); + if (toolbarElement) { + toolbarElement.appendChild(tableButton); + } + + return () => { + if (quillRef.current) { + quillRef.current = null; + } + }; + }, []); + + // Update content when props change + useEffect(() => { + if (quillRef.current && props.value !== undefined) { + const currentContent = quillRef.current.root.innerHTML; + if (currentContent !== props.value && !isCodeView) { + quillRef.current.root.innerHTML = props.value; + } + } + }, [props.value]); + + const toggleDarkMode = () => { + setIsDarkMode(!isDarkMode); + }; + + const toggleFullscreen = () => { + setIsFullscreen(!isFullscreen); + }; + + const toggleCodeView = () => { + if (isCodeView) { + // Switch back to visual editor + if (quillRef.current && codeMirrorRef.current) { + try { + const newHtml = codeMirrorRef.current.state.doc.toString(); + quillRef.current.root.innerHTML = newHtml; + if (props.onChange) { + props.onChange(newHtml); + } + } catch (error) { + console.error('Error updating editor:', error); + } + } + setIsCodeView(false); + } else { + // Switch to code view + if (quillRef.current && codeEditorRef.current) { + try { + const rawHtml = quillRef.current.root.innerHTML; + const formattedHtml = beautifyHtml(rawHtml, { + indent_size: 2, + indent_char: ' ', + max_preserve_newlines: 1, + preserve_newlines: true, + end_with_newline: false, + wrap_line_length: 0, + indent_inner_html: true, + unformatted: [], + content_unformatted: ['pre', 'textarea'] + }); + + // Initialize or update CodeMirror + if (!codeMirrorRef.current) { + const extensions = [ + basicSetup, + htmlLang(), + keymap.of([indentWithTab]), + EditorView.lineWrapping + ]; + + // Add dark theme if enabled + if (isDarkMode) { + extensions.push(oneDark); + } + + codeMirrorRef.current = new EditorView({ + state: EditorState.create({ + doc: formattedHtml, + extensions: extensions + }), + parent: codeEditorRef.current + }); + } else { + codeMirrorRef.current.dispatch({ + changes: { + from: 0, + to: codeMirrorRef.current.state.doc.length, + insert: formattedHtml + } + }); + } + + setIsCodeView(true); + } catch (error) { + console.error('Error formatting HTML:', error); + } + } + } + }; + + // Cleanup CodeMirror on unmount + useEffect(() => { + return () => { + if (codeMirrorRef.current) { + codeMirrorRef.current.destroy(); + codeMirrorRef.current = null; + } + }; + }, []); + + const containerStyles = css` + position: ${isFullscreen ? 'fixed' : 'relative'}; + top: ${isFullscreen ? '0' : 'auto'}; + left: ${isFullscreen ? '0' : 'auto'}; + width: ${isFullscreen ? '100vw' : '100%'}; + height: ${isFullscreen ? '100svh' : 'auto'}; + z-index: ${isFullscreen ? '99999' : 'auto'}; + background: ${isDarkMode ? '#191919' : '#ffffff'}; + display: flex; + flex-direction: column; + ${isFullscreen ? 'padding: 0; margin: 0;' : ''} + + .control-bar { + display: flex; + gap: 10px; + padding: 12px 16px; + background: ${isDarkMode ? '#252525' : '#f5f5f5'}; + border-bottom: 1px solid ${isDarkMode ? '#333333' : '#e0e0e0'}; + backdrop-filter: blur(10px); + position: sticky; + top: 0; + z-index: 10; + } + + .control-button { + padding: 8px 16px; + border: none; + background: ${isDarkMode ? 'rgba(255, 255, 255, 0.08)' : 'rgba(0, 0, 0, 0.05)'}; + color: ${isDarkMode ? '#ffffff' : '#1a1a1a'}; + border-radius: 8px; + cursor: pointer; + font-size: 13px; + font-weight: 500; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 6px; + + &:hover { + background: ${isDarkMode ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.08)'}; + transform: translateY(-1px); + box-shadow: ${isDarkMode + ? '0 4px 12px rgba(0, 0, 0, 0.3)' + : '0 4px 12px rgba(0, 0, 0, 0.08)'}; + } + + &:active { + transform: translateY(0); + } + + &.active { + background: ${isDarkMode ? '#0066cc' : '#0078d4'}; + color: white; + box-shadow: ${isDarkMode + ? '0 4px 12px rgba(0, 102, 204, 0.3)' + : '0 4px 12px rgba(0, 120, 212, 0.2)'}; + } + } + + .editor-wrapper { + display: ${isCodeView ? 'none' : 'flex'}; + flex-direction: column; + ${isFullscreen ? 'flex: 1; overflow: auto; padding: 20px; box-sizing: border-box;' : ''} + + > div { + ${isFullscreen ? 'max-width: 800px; width: 100%; margin: 0 auto;' : 'width: 100%;'} + } + } + + .code-editor { + ${isFullscreen ? 'flex: 1; padding: 20px;' : 'height: 400px;'} + display: ${isCodeView ? 'block' : 'none'}; + margin: 0; + width: 100%; + box-sizing: border-box; + overflow: auto; + background: ${isDarkMode ? '#191919' : '#ffffff'}; + + ${isFullscreen ? '> * { max-width: 800px; margin: 0 auto; }' : ''} + + .cm-editor { + height: ${isFullscreen ? 'calc(100svh - 85px)' : '400px'}; + font-size: 14px; + border: 1px solid ${isDarkMode ? '#393939' : '#e0e0e0'}; + + .cm-scroller { + overflow: auto; + height: 100%; + } + + .cm-gutters { + background: ${isDarkMode ? '#252525' : '#f5f5f5'}; + border-right: 1px solid ${isDarkMode ? '#333333' : '#e0e0e0'}; + } + } + } + + .ql-container { + ${isFullscreen ? 'height: calc(100svh - 150px);' : 'height: 400px;'} + overflow-y: auto; + font-size: 14px; + scroll-behavior: smooth; + + &::-webkit-scrollbar { + width: 8px; + } + + &::-webkit-scrollbar-track { + background: ${isDarkMode ? '#191919' : '#f5f5f5'}; + } + + &::-webkit-scrollbar-thumb { + background: ${isDarkMode ? '#404040' : '#c0c0c0'}; + border-radius: 4px; + } + + &::-webkit-scrollbar-thumb:hover { + background: ${isDarkMode ? '#505050' : '#a0a0a0'}; + } + } + + .ql-editor { + min-height: 100%; + background: ${isDarkMode ? '#191919' : '#ffffff'}; + color: ${isDarkMode ? '#ffffff' : '#191919'}; + padding: 20px; + font-size: 15px; + line-height: 1.6; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif; + + &:focus { + outline: none; + } + + h1, h2, h3, h4, h5, h6 { + margin-top: 24px; + margin-bottom: 12px; + font-weight: 600; + color: ${isDarkMode ? '#ffffff' : '#191919'} !important; + } + + p { + margin-bottom: 12px; + } + + ul, ol { + padding-left: 24px; + margin-bottom: 12px; + } + + blockquote { + border-left: 4px solid ${isDarkMode ? '#4da6ff' : '#0078d4'}; + padding-left: 16px; + margin-left: 0; + color: ${isDarkMode ? '#b0b0b0' : '#666666'}; + } + + code { + background: ${isDarkMode ? '#2d2d2d' : '#f5f5f5'}; + padding: 2px 6px; + border-radius: 4px; + font-family: 'Monaco', 'Courier New', monospace; + font-size: 0.9em; + } + + pre { + background: ${isDarkMode ? '#2d2d2d' : '#f5f5f5'}; + padding: 12px; + border-radius: 6px; + overflow-x: auto; + } + } + + .ql-toolbar { + background: ${isDarkMode ? '#252525' : '#ffffff'}; + border: 1px solid ${isDarkMode ? '#393939' : '#e0e0e0'}; + border-bottom: 1px solid ${isDarkMode ? '#333333' : '#e0e0e0'}; + padding: 12px 16px; + display: flex; + flex-wrap: wrap; + gap: 4px; + align-items: center; + } + + .ql-toolbar .ql-formats { + margin-right: 0px !important; + display: flex; + gap: 2px; + align-items: center; + padding: 0 4px; + border-right: 1px solid ${isDarkMode ? '#333333' : '#e0e0e0'}; + } + + .ql-toolbar .ql-formats:last-child { + border-right: none; + } + + .ql-toolbar button { + width: 32px !important; + height: 32px !important; + padding: 6px !important; + border-radius: 6px; + border: none; + background: transparent; + cursor: pointer; + transition: all 0.15s ease; + display: flex !important; + align-items: center !important; + justify-content: center !important; + } + + .ql-toolbar button svg { + display: flex; + align-items: center; + justify-content: center; + } + + .ql-toolbar .ql-stroke { + stroke: ${isDarkMode ? '#ffffff' : '#191919'}; + stroke-width: 2; + transition: all 0.15s ease; + } + + .ql-toolbar .ql-fill { + fill: ${isDarkMode ? '#ffffff' : '#191919'}; + transition: all 0.15s ease; + } + + .ql-toolbar .ql-picker { + height: 32px; + border-radius: 6px; + transition: all 0.15s ease; + } + + .ql-toolbar .ql-picker-label { + color: ${isDarkMode ? '#ffffff' : '#191919'}; + padding: 6px 10px; + border: none; + border-radius: 6px; + transition: all 0.15s ease; + display: flex !important; + align-items: center !important; + } + + .ql-toolbar .ql-picker-label:hover { + background: ${isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)'}; + } + + .ql-toolbar button:hover { + background: ${isDarkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.05)'}; + transform: translateY(-1px); + } + + .ql-toolbar button:active { + transform: translateY(0); + } + + .ql-toolbar button.ql-active { + background: ${isDarkMode ? 'rgba(0, 102, 204, 0.3)' : 'rgba(0, 120, 212, 0.15)'}; + } + + .ql-toolbar button.ql-active .ql-stroke { + stroke: ${isDarkMode ? '#4da6ff' : '#0078d4'}; + } + + .ql-toolbar button.ql-active .ql-fill { + fill: ${isDarkMode ? '#4da6ff' : '#0078d4'}; + } + + .ql-toolbar .ql-picker-label.ql-active { + background: ${isDarkMode ? 'rgba(0, 102, 204, 0.3)' : 'rgba(0, 120, 212, 0.15)'}; + color: ${isDarkMode ? '#4da6ff' : '#0078d4'}; + } + + .ql-container.ql-snow { + border: 1px solid ${isDarkMode ? '#393939' : '#e0e0e0'}; + border-top: 1px solid ${isDarkMode ? '#333333' : '#e0e0e0'}; + } + + .ql-picker-options { + background: ${isDarkMode ? '#252525' : '#ffffff'} !important; + border: 1px solid ${isDarkMode ? '#333333' : '#e0e0e0'}; + border-radius: 8px; + padding: 6px; + box-shadow: ${isDarkMode + ? '0 8px 24px rgba(0, 0, 0, 0.4)' + : '0 8px 24px rgba(0, 0, 0, 0.12)'}; + margin-top: 4px; + } + + .ql-snow .ql-picker.ql-expanded .ql-picker-options { + background: ${isDarkMode ? '#252525' : '#ffffff'} !important; + border: 1px solid ${isDarkMode ? '#333333' : '#e0e0e0'} !important; + } + + .ql-picker-item { + color: ${isDarkMode ? '#ffffff' : '#191919'} !important; + padding: 8px 12px; + border-radius: 4px; + transition: all 0.15s ease; + } + + .ql-picker-item:hover { + background: ${isDarkMode ? '#191919' : '#f0f0f0'} !important; + } + + .ql-picker-options .ql-picker-item { + color: ${isDarkMode ? '#ffffff' : '#191919'} !important; + background: transparent !important; + } + + .ql-picker-options .ql-picker-item:hover { + background: ${isDarkMode ? '#191919' : '#f0f0f0'} !important; + } + + .ql-snow .ql-picker-options .ql-picker-item { + color: ${isDarkMode ? '#ffffff' : '#191919'} !important; + background: transparent !important; + } + + .ql-snow .ql-picker-options .ql-picker-item:hover { + background: ${isDarkMode ? '#191919' : '#f0f0f0'} !important; + } + + .ql-snow .ql-picker.ql-expanded .ql-picker-options .ql-picker-item { + color: ${isDarkMode ? '#ffffff' : '#191919'} !important; + background: transparent !important; + } + + .ql-snow .ql-picker.ql-expanded .ql-picker-options .ql-picker-item:hover { + background: ${isDarkMode ? '#191919' : '#f0f0f0'} !important; + } + + /* Table styles */ + .quill-better-table-wrapper { + overflow-x: auto; + } + + .ql-table { + font-size: 20px; + padding: 3px 5px; + } + + /* Code block syntax highlighting */ + .ql-editor pre.ql-syntax { + background-color: ${isDarkMode ? '#282c34' : '#f5f5f5'}; + color: ${isDarkMode ? '#abb2bf' : '#383a42'}; + overflow: visible; + border-radius: 4px; + padding: 16px; + margin: 8px 0; + } + + /* Code inline */ + .ql-editor code { + background-color: ${isDarkMode ? '#2d2d2d' : '#f4f4f4'}; + color: ${isDarkMode ? '#e06c75' : '#e45649'}; + padding: 2px 6px; + border-radius: 3px; + font-family: 'Courier New', monospace; + } + + /* Blockquote */ + .ql-editor blockquote { + border-left: 4px solid ${isDarkMode ? '#61afef' : '#007acc'}; + background: ${isDarkMode ? '#2d2d2d' : '#f9f9f9'}; + padding: 10px 20px; + margin: 8px 0; + } + + /* Links */ + .ql-editor a { + color: ${isDarkMode ? '#61afef' : '#0066cc'}; + text-decoration: underline; + } + + /* Images and videos */ + .ql-editor img, + .ql-editor video { + max-width: 100%; + height: auto; + border-radius: 4px; + margin: 8px 0; + } + `; + + return ( +
+
+ + + +
+ +
+
+
+ +
+
+ ); +} + +// Register the editor with Builder +Builder.registerEditor({ + name: 'RichText', + component: RichTextEditor +}); diff --git a/plugins/Better-Quill-Plugin/webpack.config.js b/plugins/Better-Quill-Plugin/webpack.config.js new file mode 100644 index 00000000000..5c33a393316 --- /dev/null +++ b/plugins/Better-Quill-Plugin/webpack.config.js @@ -0,0 +1,48 @@ +const path = require('path'); +const pkg = require('./package.json'); + +module.exports = { + entry: `./src/${pkg.entry}.jsx`, + externals: { + '@builder.io/react': '@builder.io/react', + '@builder.io/app-context': '@builder.io/app-context', + "@emotion/core": "@emotion/core", + "react": "react", + "react-dom": "react-dom" + }, + output: { + filename: pkg.output, + path: path.resolve(__dirname, 'dist'), + libraryTarget: 'system', + }, + resolve: { + extensions: ['.js', '.jsx'], + }, + module: { + rules: [ + { + test: /\.(jsx)$/, + exclude: /node_modules/, + use: [ + { + loader: 'babel-loader', + }, + ], + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, + ], + }, + devServer: { + port: 1268, + static: { + directory: path.join(__dirname, './dist'), + }, + headers: { + 'Access-Control-Allow-Private-Network': 'true', + 'Access-Control-Allow-Origin': '*', + }, + }, +};