diff --git a/packages/quill/package.json b/packages/quill/package.json index 1ce5f24dc5..dddecfdb46 100644 --- a/packages/quill/package.json +++ b/packages/quill/package.json @@ -1,6 +1,6 @@ { - "name": "quill", - "version": "2.0.2", + "name": "hoavm-quill", + "version": "2.0.2-alpha1.20", "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 3a08604e13..629ec972d4 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 e68f56a0b3..81aaadc694 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 5412355b85..687a0fb790 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/scriptTag.ts b/packages/quill/src/formats/scriptTag.ts new file mode 100644 index 0000000000..16f0304234 --- /dev/null +++ b/packages/quill/src/formats/scriptTag.ts @@ -0,0 +1,8 @@ +import Embed from '../blots/embed.js'; + +class ScriptTag extends Embed { + static blotName = 'script'; + static tagName = 'SCRIPT'; +} + +export default ScriptTag; diff --git a/packages/quill/src/formats/size.ts b/packages/quill/src/formats/size.ts index 1f84a6f4e4..b33b86d7a5 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 84d4bb15cf..551907f6e8 100644 --- a/packages/quill/src/formats/video.ts +++ b/packages/quill/src/formats/video.ts @@ -1,18 +1,40 @@ import { BlockEmbed } from '../blots/block.js'; import Link from './link.js'; -const ATTRIBUTES = ['height', 'width']; +const ATTRIBUTES = [ + 'src', + 'srcdoc', + 'name', + 'width', + 'height', + 'frameborder', + 'allow', + 'allowfullscreen', + 'sandbox', + 'referrerpolicy', + 'loading', + 'longdesc', + 'title', + 'class', + 'id', + 'style', + 'tabindex', + 'draggable', + 'scrolling', +]; class Video extends BlockEmbed { static blotName = 'video'; static className = 'ql-video'; static tagName = 'IFRAME'; - static create(value: string) { - const node = super.create(value) as Element; - node.setAttribute('frameborder', '0'); - node.setAttribute('allowfullscreen', 'true'); - node.setAttribute('src', this.sanitize(value)); + static create(value: any) { + const node = document.createElement('iframe'); + ATTRIBUTES.forEach((attr) => { + if (value[attr]) { + node.setAttribute(attr, value[attr]); + } + }); return node; } @@ -33,7 +55,10 @@ class Video extends BlockEmbed { } static value(domNode: Element) { - return domNode.getAttribute('src'); + return ATTRIBUTES.reduce((acc: any, attr) => { + acc[attr] = domNode.getAttribute(attr); + return acc; + }, {}); } domNode: HTMLVideoElement; @@ -51,8 +76,7 @@ class Video extends BlockEmbed { } html() { - const { video } = this.value(); - return `${video}`; + return Video.create(this.value().video).outerHTML; } } diff --git a/packages/quill/src/quill.ts b/packages/quill/src/quill.ts index 9ae4198430..80a5a06658 100644 --- a/packages/quill/src/quill.ts +++ b/packages/quill/src/quill.ts @@ -1,4 +1,4 @@ -import Quill from './core.js'; +import Quill, { Parchment, Range } from './core.js'; import type { Bounds, DebugLevel, @@ -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, @@ -115,15 +117,7 @@ Quill.register( true, ); -export { - AttributeMap, - Delta, - Module, - Op, - OpIterator, - Parchment, - Range, -} from './core.js'; +export { Module } from './core.js'; export type { Bounds, DebugLevel, @@ -131,5 +125,6 @@ export type { ExpandedQuillOptions, QuillOptions, }; +export { Parchment, Range }; export default Quill; diff --git a/packages/quill/test/types/quill.test-d.ts b/packages/quill/test/types/quill.test-d.ts index a46bc216d2..1a2ca74040 100644 --- a/packages/quill/test/types/quill.test-d.ts +++ b/packages/quill/test/types/quill.test-d.ts @@ -1,6 +1,7 @@ import { assertType, expectTypeOf } from 'vitest'; -import Quill, { Delta } from '../../src/quill.js'; +import Quill from '../../src/quill.js'; import type { EmitterSource, Parchment, Range } from '../../src/quill.js'; +import Delta from 'quill-delta'; import type { default as Block, BlockEmbed } from '../../src/blots/block.js'; import SnowTheme from '../../src/themes/snow.js'; import { LeafBlot } from 'parchment';