diff --git a/packages/quill/package.json b/packages/quill/package.json index dd2ff1884..4d2a9d9c3 100644 --- a/packages/quill/package.json +++ b/packages/quill/package.json @@ -1,6 +1,6 @@ { "name": "hoavm-quill", - "version": "2.0.2-alpha1.13", + "version": "2.0.2-alpha1.15", "description": "Your powerful, rich text editor", "author": "Jason Chen ", "homepage": "https://quilljs.com", diff --git a/packages/quill/src/formats/font.ts b/packages/quill/src/formats/font.ts index 3a08604e1..629ec972d 100644 --- a/packages/quill/src/formats/font.ts +++ b/packages/quill/src/formats/font.ts @@ -2,7 +2,14 @@ import { ClassAttributor, Scope, StyleAttributor } from 'parchment'; const config = { scope: Scope.INLINE, - whitelist: ['serif', 'monospace'], + whitelist: [ + 'arial', + 'comic-sans', + 'courier-new', + 'georgia', + 'helvetica', + 'lucida', + ], }; const FontClass = new ClassAttributor('font', 'ql-font', config); diff --git a/packages/quill/src/formats/image.ts b/packages/quill/src/formats/image.ts index e68f56a0b..81aaadc69 100644 --- a/packages/quill/src/formats/image.ts +++ b/packages/quill/src/formats/image.ts @@ -1,17 +1,44 @@ import { EmbedBlot } from 'parchment'; -import { sanitize } from './link.js'; +// import { sanitize } from './link.js'; -const ATTRIBUTES = ['alt', 'height', 'width']; +const ATTRIBUTES = [ + 'alt', + 'height', + 'width', + 'src', + 'srcset', + 'sizes', + 'crossorigin', + 'usemap', + 'ismap', + 'loading', + 'referrerpolicy', + 'decoding', + 'longdesc', + 'title', + 'class', + 'id', + 'style', + 'tabindex', + 'draggable', + 'align', + 'border', + 'hspace', + 'vspace', + 'accesskey', +]; class Image extends EmbedBlot { static blotName = 'image'; static tagName = 'IMG'; - static create(value: string) { - const node = super.create(value) as Element; - if (typeof value === 'string') { - node.setAttribute('src', this.sanitize(value)); - } + static create(value: any) { + const node = document.createElement('img'); + ATTRIBUTES.forEach((attr) => { + if (value[attr]) { + node.setAttribute(attr, value[attr]); + } + }); return node; } @@ -32,11 +59,14 @@ class Image extends EmbedBlot { } static sanitize(url: string) { - return sanitize(url, ['http', 'https', 'data']) ? url : '//:0'; + return url; } static value(domNode: Element) { - return domNode.getAttribute('src'); + return ATTRIBUTES.reduce((acc: any, attr) => { + acc[attr] = domNode.getAttribute(attr); + return acc; + }, {}); } domNode: HTMLImageElement; diff --git a/packages/quill/src/formats/link.ts b/packages/quill/src/formats/link.ts index 5412355b8..687a0fb79 100644 --- a/packages/quill/src/formats/link.ts +++ b/packages/quill/src/formats/link.ts @@ -7,29 +7,65 @@ class Link extends Inline { static PROTOCOL_WHITELIST = ['http', 'https', 'mailto', 'tel', 'sms']; static create(value: string) { - const node = super.create(value) as HTMLElement; - node.setAttribute('href', this.sanitize(value)); - node.setAttribute('rel', 'noopener noreferrer'); - node.setAttribute('target', '_blank'); + const node = super.create(); + let newValue; + if (isStringified(value)) { + newValue = JSON.parse(value); + } else { + newValue = value; + } + + if (typeof newValue !== 'string') { + ['href', 'target', 'title'].forEach((attr) => { + if (newValue[attr]) node.setAttribute(attr, newValue[attr]); + }); + return node; + } + + node.setAttribute('href', newValue); return node; } static formats(domNode: HTMLElement) { - return domNode.getAttribute('href'); + return JSON.stringify({ + href: domNode.getAttribute('href'), + target: domNode.getAttribute('target'), + title: domNode.getAttribute('title'), + }); } static sanitize(url: string) { return sanitize(url, this.PROTOCOL_WHITELIST) ? url : this.SANITIZED_URL; } - format(name: string, value: unknown) { + format(name: string, value: any) { if (name !== this.statics.blotName || !value) { - super.format(name, value); + return super.format(name, value); + } + let newValue; + if (isStringified(value)) { + newValue = JSON.parse(value); } else { - // @ts-expect-error - this.domNode.setAttribute('href', this.constructor.sanitize(value)); + newValue = value; } + + if (typeof newValue !== 'string') { + this.domNode.setAttribute('href', newValue.href); + this.domNode.setAttribute('target', newValue.target); + this.domNode.setAttribute('title', newValue.title); + } else { + this.domNode.setAttribute('href', newValue); + } + } +} + +function isStringified(value: any) { + try { + JSON.parse(value); + } catch (e) { + return false; } + return true; } function sanitize(url: string, protocols: string[]) { diff --git a/packages/quill/src/formats/list.ts b/packages/quill/src/formats/list.ts index ed1352fb1..ef9ffbf73 100644 --- a/packages/quill/src/formats/list.ts +++ b/packages/quill/src/formats/list.ts @@ -5,7 +5,7 @@ import Quill from '../core/quill.js'; class ListContainer extends Container {} ListContainer.blotName = 'list-container'; -ListContainer.tagName = 'OL'; +ListContainer.tagName = ['OL', 'UL']; // Support both ordered and unordered lists class ListItem extends Block { static create(value: string) { @@ -15,7 +15,11 @@ class ListItem extends Block { } static formats(domNode: HTMLElement) { - return domNode.getAttribute('data-list') || undefined; + // Handle more formats for list types: ordered, unordered, checked, unchecked + const format = domNode.getAttribute('data-list') || ''; + return ['ordered', 'bullet', 'checked', 'unchecked'].includes(format) + ? format + : undefined; } static register() { @@ -24,26 +28,37 @@ class ListItem extends Block { constructor(scroll: Scroll, domNode: HTMLElement) { super(scroll, domNode); - const ui = domNode.ownerDocument.createElement('span'); - const listEventHandler = (e: Event) => { - if (!scroll.isEnabled()) return; - const format = this.statics.formats(domNode, scroll); - if (format === 'checked') { - this.format('list', 'unchecked'); - e.preventDefault(); - } else if (format === 'unchecked') { - this.format('list', 'checked'); - e.preventDefault(); - } - }; - ui.addEventListener('mousedown', listEventHandler); - ui.addEventListener('touchstart', listEventHandler); - this.attachUI(ui); + const format = this.statics.formats(domNode); + + // Create UI for checkbox if the list format is checked or unchecked + if (['checked', 'unchecked'].includes(format)) { + const ui = domNode.ownerDocument.createElement('span'); + ui.classList.add('list-ui'); // Add a CSS class for styling + + const listEventHandler = (e: Event) => { + if (!scroll.isEnabled()) return; + + if (format === 'checked') { + this.format('list', 'unchecked'); + e.preventDefault(); + } else if (format === 'unchecked') { + this.format('list', 'checked'); + e.preventDefault(); + } + }; + + ui.addEventListener('mousedown', listEventHandler); + ui.addEventListener('touchstart', listEventHandler); + this.attachUI(ui); + } } format(name: string, value: string) { + // Extend format handling to include more list types if (name === this.statics.blotName && value) { - this.domNode.setAttribute('data-list', value); + if (['ordered', 'bullet', 'checked', 'unchecked'].includes(value)) { + this.domNode.setAttribute('data-list', value); + } } else { super.format(name, value); } @@ -52,6 +67,7 @@ class ListItem extends Block { ListItem.blotName = 'list'; ListItem.tagName = 'LI'; +// Allow both ordered (OL) and unordered (UL) containers ListContainer.allowedChildren = [ListItem]; ListItem.requiredContainer = ListContainer; diff --git a/packages/quill/src/formats/scriptTag.ts b/packages/quill/src/formats/scriptTag.ts new file mode 100644 index 000000000..10d11251b --- /dev/null +++ b/packages/quill/src/formats/scriptTag.ts @@ -0,0 +1,28 @@ +import Block from '../blots/block.js'; + +class ScriptTag extends Block { + static blotName = 'script'; + static tagName = 'SCRIPT'; + + static insertAt() { + return; + } + + static insertBefore() { + return; + } + + static replaceWith() { + return; + } + + format() { + return; + } + + formatAt() { + return; + } +} + +export default ScriptTag; diff --git a/packages/quill/src/formats/size.ts b/packages/quill/src/formats/size.ts index 1f84a6f4e..b33b86d7a 100644 --- a/packages/quill/src/formats/size.ts +++ b/packages/quill/src/formats/size.ts @@ -2,7 +2,7 @@ import { ClassAttributor, Scope, StyleAttributor } from 'parchment'; const SizeClass = new ClassAttributor('size', 'ql-size', { scope: Scope.INLINE, - whitelist: ['small', 'large', 'huge'], + whitelist: ['extra-small', 'small', 'medium', 'large'], }); const SizeStyle = new StyleAttributor('size', 'font-size', { scope: Scope.INLINE, diff --git a/packages/quill/src/formats/video.ts b/packages/quill/src/formats/video.ts index fd9c1198a..551907f6e 100644 --- a/packages/quill/src/formats/video.ts +++ b/packages/quill/src/formats/video.ts @@ -2,19 +2,25 @@ import { BlockEmbed } from '../blots/block.js'; import Link from './link.js'; const ATTRIBUTES = [ - 'title', - 'sandbox', - 'referrerpolicy', - 'name', 'src', 'srcdoc', + 'name', 'width', 'height', 'frameborder', - 'allowfullscreen', 'allow', + 'allowfullscreen', + 'sandbox', + 'referrerpolicy', 'loading', - 'allowpaymentrequest', + 'longdesc', + 'title', + 'class', + 'id', + 'style', + 'tabindex', + 'draggable', + 'scrolling', ]; class Video extends BlockEmbed { diff --git a/packages/quill/src/quill.ts b/packages/quill/src/quill.ts index 9ae419843..0366c6851 100644 --- a/packages/quill/src/quill.ts +++ b/packages/quill/src/quill.ts @@ -30,6 +30,7 @@ import Link from './formats/link.js'; import Script from './formats/script.js'; import Strike from './formats/strike.js'; import Underline from './formats/underline.js'; +import ScriptTag from './formats/scriptTag.js'; import Formula from './formats/formula.js'; import Image from './formats/image.js'; @@ -98,6 +99,7 @@ Quill.register( 'formats/formula': Formula, 'formats/image': Image, 'formats/video': Video, + 'formats/scriptTag': ScriptTag, 'modules/syntax': Syntax, 'modules/table': Table,