From 732c60ea02f15f5ba72889d6bd4a6c3bb4fbdb81 Mon Sep 17 00:00:00 2001 From: Dmitriy Davydov Date: Wed, 9 Jul 2025 11:42:49 +0200 Subject: [PATCH 01/14] Add playground --- packages/quill/src/playground/index.html | 396 +++++++++++++++++++++++ packages/quill/webpack.common.cjs | 11 + 2 files changed, 407 insertions(+) create mode 100644 packages/quill/src/playground/index.html diff --git a/packages/quill/src/playground/index.html b/packages/quill/src/playground/index.html new file mode 100644 index 0000000000..e6ebab843b --- /dev/null +++ b/packages/quill/src/playground/index.html @@ -0,0 +1,396 @@ + + + + + 🤖 Quill Suggestions Playground + + + + + +
+
+

🤖 Quill Suggestions Playground

+

Hackathon prototype for AI autocompletion feature

+ Generated by webpack HtmlPlugin â€ĸ Built: <%= buildTime %> â€ĸ Mode: <%= env %> +
+ +
+
+

Hello world!

+

Welcome to the Quill Suggestions playground. Start typing and test the AI autocompletion features below.

+


+
+ +
+
+

🤖 Suggestions API Testing

+
+ + + +
+
+ + +
+
Initializing...
+
+ Shortcuts: Cmd+Enter (suggest), Cmd+S (accept), Cmd+D (cancel) +
+
+ +
+

â„šī¸ Development Info

+
    +
  • Server: localhost:9080
  • +
  • Auto-rebuild: ✅ Enabled
  • +
  • Source maps: ✅ Available
  • +
  • Template: HtmlWebpackPlugin
  • +
  • Build mode: <%= env %>
  • +
+ +
+ API Implementation Progress:
+
+ suggestionsStart() + suggestionsAccept() + suggestionsCancel() +
+
+ +
+ Next Steps:
+ 1. Edit Quill source files in src/
+ 2. Webpack auto-rebuilds on changes
+ 3. Test API methods here immediately +
+
+
+
+
+ + + + + diff --git a/packages/quill/webpack.common.cjs b/packages/quill/webpack.common.cjs index cd370cb061..6d393c9fa9 100644 --- a/packages/quill/webpack.common.cjs +++ b/packages/quill/webpack.common.cjs @@ -2,6 +2,7 @@ const { resolve } = require('path'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); const tsRules = { test: /\.ts$/, @@ -65,5 +66,15 @@ module.exports = { new MiniCssExtractPlugin({ filename: '[name]', }), + new HtmlWebpackPlugin({ + template: resolve(__dirname, 'src/playground/index.html'), + filename: resolve(__dirname, 'dist/index.html'), + inject: false, // Manual control over script/CSS loading + minify: false, + templateParameters: { + buildTime: new Date().toISOString(), + env: process.env.NODE_ENV || 'development', + }, + }), ], }; From 220f9f9f8d917ffbd77f451b47e6102a782a2a84 Mon Sep 17 00:00:00 2001 From: Dmitriy Davydov Date: Wed, 9 Jul 2025 12:27:57 +0200 Subject: [PATCH 02/14] Initial suggestion version using insertText method --- packages/quill/src/assets/core.styl | 26 +++++++ packages/quill/src/core.ts | 2 + packages/quill/src/core/quill.ts | 111 ++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+) diff --git a/packages/quill/src/assets/core.styl b/packages/quill/src/assets/core.styl index b4645ca6fc..265530f024 100644 --- a/packages/quill/src/assets/core.styl +++ b/packages/quill/src/assets/core.styl @@ -202,6 +202,32 @@ resets(arr) .ql-align-right text-align: right +// Suggestion text styling for hackathon prototype +.ql-suggestion-text + color: #999 + background: rgba(0, 123, 255, 0.05) + font-style: italic + user-select: none + pointer-events: none + position: relative + border-radius: 2px + padding: 0 1px + + // Animation + animation: suggestionFadeIn 0.3s ease-in-out + +@keyframes suggestionFadeIn + from + opacity: 0 + transform: translateY(-1px) + to + opacity: 1 + transform: translateY(0) + +// Suggestions mode indicator +.ql-editor.ql-suggestions-mode + // Optional: add mode indicators if needed + .ql-ui position: absolute diff --git a/packages/quill/src/core.ts b/packages/quill/src/core.ts index 65d0df47d9..d5a64fd2d8 100644 --- a/packages/quill/src/core.ts +++ b/packages/quill/src/core.ts @@ -16,6 +16,7 @@ import Inline from './blots/inline.js'; import Scroll from './blots/scroll.js'; import TextBlot from './blots/text.js'; import SoftBreak from './blots/soft-break.js'; +import SuggestionTextBlot from './blots/suggestion-text.js'; import Clipboard from './modules/clipboard.js'; import History from './modules/history.js'; @@ -46,6 +47,7 @@ Quill.register({ 'blots/inline': Inline, 'blots/scroll': Scroll, 'blots/text': TextBlot, + 'blots/suggestion-text': SuggestionTextBlot, 'modules/clipboard': Clipboard, 'modules/history': History, diff --git a/packages/quill/src/core/quill.ts b/packages/quill/src/core/quill.ts index c8ee8b89a1..2fe0cce2f7 100644 --- a/packages/quill/src/core/quill.ts +++ b/packages/quill/src/core/quill.ts @@ -194,6 +194,10 @@ class Quill { options: ExpandedQuillOptions; + // Suggestions state + private isSuggestionsMode: boolean = false; + private primaryDelta: Delta | null = null; + constructor(container: HTMLElement | string, options: QuillOptions = {}) { this.options = expandConfig(container, options); this.container = this.options.container; @@ -619,6 +623,23 @@ class Quill { // eslint-disable-next-line prefer-const // @ts-expect-error [index, , formats, source] = overload(index, 0, name, value, source); + + // Handle suggestions mode + if (this.isSuggestionsMode) { + return modify.call( + this, + () => { + // Insert text with suggestion format + return this.editor.insertText(index, text, { + 'suggestion-text': true, + }); + }, + source, + index, + text.length, + ); + } + return modify.call( this, () => { @@ -778,6 +799,96 @@ class Quill { true, ); } + + // Suggestions API methods + suggestionsStart(): void { + if (this.isSuggestionsMode) return; + + this.primaryDelta = this.getContents(); + this.isSuggestionsMode = true; + this.scroll.batchStart(); + this.container.classList.add('ql-suggestions-mode'); + } + + suggestionsAccept(): Delta { + if (!this.isSuggestionsMode) return new Delta(); + + // Convert suggestion blots to regular text blots + const changes = this.convertSuggestionsToText(); + this.scroll.batchEnd(); + this.container.classList.remove('ql-suggestions-mode'); + this.isSuggestionsMode = false; + + if (changes.length() > 0) { + this.emitter.emit( + Emitter.events.TEXT_CHANGE, + changes, + this.primaryDelta, + Emitter.sources.API, + ); + } + + return changes; + } + + suggestionsCancel(): void { + if (!this.isSuggestionsMode) return; + + this.removeSuggestionBlots(); + this.scroll.batchEnd(); + this.container.classList.remove('ql-suggestions-mode'); + this.isSuggestionsMode = false; + } + + private convertSuggestionsToText(): Delta { + const changes = new Delta(); + const currentDelta = this.getContents(); + + // Create a new delta without suggestion formatting + currentDelta.ops.forEach((op, index) => { + if ( + op.insert && + typeof op.insert === 'string' && + op.attributes && + op.attributes['suggestion-text'] + ) { + // Remove suggestion format, keep the text + const cleanAttributes = { ...op.attributes }; + delete cleanAttributes['suggestion-text']; + changes.insert( + op.insert, + Object.keys(cleanAttributes).length > 0 ? cleanAttributes : undefined, + ); + } else { + changes.push(op); + } + }); + + // Apply the cleaned content + this.setContents(changes, Emitter.sources.SILENT); + return changes; + } + + private removeSuggestionBlots(): void { + const currentDelta = this.getContents(); + const cleanDelta = new Delta(); + + // Remove all text with suggestion formatting + currentDelta.ops.forEach((op) => { + if ( + op.insert && + typeof op.insert === 'string' && + op.attributes && + op.attributes['suggestion-text'] + ) { + // Skip this text (remove it) + } else { + cleanDelta.push(op); + } + }); + + this.setContents(cleanDelta, Emitter.sources.SILENT); + } } function resolveSelector(selector: string | HTMLElement | null | undefined) { From ae243d1d585030a29d75fe8143593dad0eda391c Mon Sep 17 00:00:00 2001 From: Dmitriy Davydov Date: Wed, 9 Jul 2025 13:04:48 +0200 Subject: [PATCH 03/14] Make the test playground work as expected --- packages/quill/package.json | 6 ++++- packages/quill/src/blots/suggestion-text.ts | 25 +++++++++++++++++++++ packages/quill/src/core/quill.ts | 2 +- packages/quill/webpack.config.cjs | 11 ++++++++- 4 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 packages/quill/src/blots/suggestion-text.ts diff --git a/packages/quill/package.json b/packages/quill/package.json index 167716b3ee..91901befda 100644 --- a/packages/quill/package.json +++ b/packages/quill/package.json @@ -76,10 +76,14 @@ ], "scripts": { "build": "./scripts/build production", + "build:watch": "webpack --mode development --watch", + "dev": "npm run build:dev && npm run build:watch", "lint": "run-s lint:*", "lint:eslint": "eslint .", "lint:tsc": "tsc --noEmit --skipLibCheck", - "start": "[[ -z \"$npm_package_config_ports_webpack\" ]] && webpack-dev-server || webpack-dev-server --port $npm_package_config_ports_webpack", + "start": "webpack serve --mode development --open", + "start:quill": "npm run start", + "build:dev": "webpack --mode development", "test": "run-s test:*", "test:unit": "vitest --config test/unit/vitest.config.ts", "test:fuzz": "vitest --config test/fuzz/vitest.config.ts", diff --git a/packages/quill/src/blots/suggestion-text.ts b/packages/quill/src/blots/suggestion-text.ts new file mode 100644 index 0000000000..47f1bff3a6 --- /dev/null +++ b/packages/quill/src/blots/suggestion-text.ts @@ -0,0 +1,25 @@ +import Inline from './inline.js'; + +class SuggestionTextBlot extends Inline { + static blotName = 'suggestion-text'; + static className = 'ql-suggestion-text'; + static tagName = 'SPAN'; + + static create() { + const node = super.create(); + node.setAttribute('contenteditable', 'false'); + node.classList.add(this.className); + return node; + } + + static formats() { + return true; + } + + // Prevent merging with regular inline blots + optimize() { + // Skip optimization to maintain suggestion separation + } +} + +export default SuggestionTextBlot; diff --git a/packages/quill/src/core/quill.ts b/packages/quill/src/core/quill.ts index 2fe0cce2f7..a78665c2b1 100644 --- a/packages/quill/src/core/quill.ts +++ b/packages/quill/src/core/quill.ts @@ -636,7 +636,7 @@ class Quill { }, source, index, - text.length, + 0, ); } diff --git a/packages/quill/webpack.config.cjs b/packages/quill/webpack.config.cjs index 41d6c907f8..f51eb58ada 100644 --- a/packages/quill/webpack.config.cjs +++ b/packages/quill/webpack.config.cjs @@ -32,11 +32,20 @@ module.exports = (env) => static: { directory: resolve(__dirname, './dist'), }, - hot: false, + hot: true, + liveReload: true, allowedHosts: 'all', devMiddleware: { stats: 'minimal', + writeToDisk: true, // Write files to disk so they're available }, + watchFiles: ['src/**/*'], }, stats: 'minimal', + // Enable watching in development mode + watch: !env.production, + watchOptions: { + ignored: /node_modules/, + poll: 1000, + }, }); From c2bc447f79203ecc4ff5acf4ddd1cbed49418395 Mon Sep 17 00:00:00 2001 From: Dmitriy Davydov Date: Wed, 9 Jul 2025 13:22:37 +0200 Subject: [PATCH 04/14] Introduce SuggestionModule --- packages/quill/src/assets/core.styl | 3 - packages/quill/src/core.ts | 2 + packages/quill/src/core/quill.ts | 3 + packages/quill/src/playground/index.html | 325 +++++++++++++++-------- 4 files changed, 214 insertions(+), 119 deletions(-) diff --git a/packages/quill/src/assets/core.styl b/packages/quill/src/assets/core.styl index 265530f024..66c0620380 100644 --- a/packages/quill/src/assets/core.styl +++ b/packages/quill/src/assets/core.styl @@ -205,13 +205,10 @@ resets(arr) // Suggestion text styling for hackathon prototype .ql-suggestion-text color: #999 - background: rgba(0, 123, 255, 0.05) font-style: italic user-select: none pointer-events: none position: relative - border-radius: 2px - padding: 0 1px // Animation animation: suggestionFadeIn 0.3s ease-in-out diff --git a/packages/quill/src/core.ts b/packages/quill/src/core.ts index d5a64fd2d8..512d935995 100644 --- a/packages/quill/src/core.ts +++ b/packages/quill/src/core.ts @@ -22,6 +22,7 @@ import Clipboard from './modules/clipboard.js'; import History from './modules/history.js'; import Keyboard from './modules/keyboard.js'; import Uploader from './modules/uploader.js'; +import Suggestions from './modules/suggestions.js'; import Delta, { Op, OpIterator, AttributeMap } from 'quill-delta'; import Input from './modules/input.js'; import UINode from './modules/uiNode.js'; @@ -53,6 +54,7 @@ Quill.register({ 'modules/history': History, 'modules/keyboard': Keyboard, 'modules/uploader': Uploader, + 'modules/suggestions': Suggestions, 'modules/input': Input, 'modules/uiNode': UINode, }); diff --git a/packages/quill/src/core/quill.ts b/packages/quill/src/core/quill.ts index a78665c2b1..884ea7933b 100644 --- a/packages/quill/src/core/quill.ts +++ b/packages/quill/src/core/quill.ts @@ -9,6 +9,7 @@ import type Clipboard from '../modules/clipboard.js'; import type History from '../modules/history.js'; import type Keyboard from '../modules/keyboard.js'; import type Uploader from '../modules/uploader.js'; +import type Suggestions from '../modules/suggestions.js'; import Editor from './editor.js'; import Emitter from './emitter.js'; import type { EmitterSource } from './emitter.js'; @@ -81,6 +82,7 @@ class Quill { keyboard: true, history: true, uploader: true, + suggestions: true, }, placeholder: '', readOnly: false, @@ -189,6 +191,7 @@ class Quill { clipboard: Clipboard; history: History; uploader: Uploader; + suggestions: Suggestions; tempFocusHolder: HTMLInputElement; diff --git a/packages/quill/src/playground/index.html b/packages/quill/src/playground/index.html index e6ebab843b..a34a280ea7 100644 --- a/packages/quill/src/playground/index.html +++ b/packages/quill/src/playground/index.html @@ -6,34 +6,34 @@ @@ -167,14 +145,14 @@

🤖 Quill Suggestions Playground

Hackathon prototype for AI autocompletion feature

Generated by webpack HtmlPlugin â€ĸ Built: <%= buildTime %> â€ĸ Mode: <%= env %> - +

Hello world!

Welcome to the Quill Suggestions playground. Start typing and test the AI autocompletion features below.


- +

🤖 Suggestions API Testing

@@ -186,13 +164,15 @@

🤖 Suggestions API Testing

+
Initializing...
- Shortcuts: Cmd+Enter (suggest), Cmd+S (accept), Cmd+D (cancel) + đŸŽ¯ New Shortcuts: Cmd+; (trigger), Tab (accept), Esc (cancel)
+ Legacy: Cmd+Enter (suggest), Cmd+S (accept), Cmd+D (cancel)
- +

â„šī¸ Development Info

    @@ -202,7 +182,7 @@

    â„šī¸ Development Info

  • Template: HtmlWebpackPlugin
  • Build mode: <%= env %>
- +
API Implementation Progress:
@@ -211,7 +191,7 @@

â„šī¸ Development Info

suggestionsCancel()
- +
Next Steps:
1. Edit Quill source files in src/
@@ -225,7 +205,7 @@

â„šī¸ Development Info