| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628 |
- import { merge } from 'lodash-es';
- import * as Parchment from 'parchment';
- import Delta from 'quill-delta';
- import Editor from './editor.js';
- import Emitter from './emitter.js';
- import instances from './instances.js';
- import logger from './logger.js';
- import Module from './module.js';
- import Selection, { Range } from './selection.js';
- import Composition from './composition.js';
- import Theme from './theme.js';
- import scrollRectIntoView from './utils/scrollRectIntoView.js';
- import createRegistryWithFormats from './utils/createRegistryWithFormats.js';
- const debug = logger('quill');
- const globalRegistry = new Parchment.Registry();
- Parchment.ParentBlot.uiClass = 'ql-ui';
- /**
- * Options for initializing a Quill instance
- */
- /**
- * Similar to QuillOptions, but with all properties expanded to their default values,
- * and all selectors resolved to HTMLElements.
- */
- class Quill {
- static DEFAULTS = {
- bounds: null,
- modules: {
- clipboard: true,
- keyboard: true,
- history: true,
- uploader: true
- },
- placeholder: '',
- readOnly: false,
- registry: globalRegistry,
- theme: 'default'
- };
- static events = Emitter.events;
- static sources = Emitter.sources;
- static version = typeof "2.0.2" === 'undefined' ? 'dev' : "2.0.2";
- static imports = {
- delta: Delta,
- parchment: Parchment,
- 'core/module': Module,
- 'core/theme': Theme
- };
- static debug(limit) {
- if (limit === true) {
- limit = 'log';
- }
- logger.level(limit);
- }
- static find(node) {
- let bubble = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
- return instances.get(node) || globalRegistry.find(node, bubble);
- }
- static import(name) {
- if (this.imports[name] == null) {
- debug.error(`Cannot import ${name}. Are you sure it was registered?`);
- }
- return this.imports[name];
- }
- static register() {
- if (typeof (arguments.length <= 0 ? undefined : arguments[0]) !== 'string') {
- const target = arguments.length <= 0 ? undefined : arguments[0];
- const overwrite = !!(arguments.length <= 1 ? undefined : arguments[1]);
- const name = 'attrName' in target ? target.attrName : target.blotName;
- if (typeof name === 'string') {
- // Shortcut for formats:
- // register(Blot | Attributor, overwrite)
- this.register(`formats/${name}`, target, overwrite);
- } else {
- Object.keys(target).forEach(key => {
- this.register(key, target[key], overwrite);
- });
- }
- } else {
- const path = arguments.length <= 0 ? undefined : arguments[0];
- const target = arguments.length <= 1 ? undefined : arguments[1];
- const overwrite = !!(arguments.length <= 2 ? undefined : arguments[2]);
- if (this.imports[path] != null && !overwrite) {
- debug.warn(`Overwriting ${path} with`, target);
- }
- this.imports[path] = target;
- if ((path.startsWith('blots/') || path.startsWith('formats/')) && target && typeof target !== 'boolean' && target.blotName !== 'abstract') {
- globalRegistry.register(target);
- }
- if (typeof target.register === 'function') {
- target.register(globalRegistry);
- }
- }
- }
- constructor(container) {
- let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- this.options = expandConfig(container, options);
- this.container = this.options.container;
- if (this.container == null) {
- debug.error('Invalid Quill container', container);
- return;
- }
- if (this.options.debug) {
- Quill.debug(this.options.debug);
- }
- const html = this.container.innerHTML.trim();
- this.container.classList.add('ql-container');
- this.container.innerHTML = '';
- instances.set(this.container, this);
- this.root = this.addContainer('ql-editor');
- this.root.classList.add('ql-blank');
- this.emitter = new Emitter();
- const scrollBlotName = Parchment.ScrollBlot.blotName;
- const ScrollBlot = this.options.registry.query(scrollBlotName);
- if (!ScrollBlot || !('blotName' in ScrollBlot)) {
- throw new Error(`Cannot initialize Quill without "${scrollBlotName}" blot`);
- }
- this.scroll = new ScrollBlot(this.options.registry, this.root, {
- emitter: this.emitter
- });
- this.editor = new Editor(this.scroll);
- this.selection = new Selection(this.scroll, this.emitter);
- this.composition = new Composition(this.scroll, this.emitter);
- this.theme = new this.options.theme(this, this.options); // eslint-disable-line new-cap
- this.keyboard = this.theme.addModule('keyboard');
- this.clipboard = this.theme.addModule('clipboard');
- this.history = this.theme.addModule('history');
- this.uploader = this.theme.addModule('uploader');
- this.theme.addModule('input');
- this.theme.addModule('uiNode');
- this.theme.init();
- this.emitter.on(Emitter.events.EDITOR_CHANGE, type => {
- if (type === Emitter.events.TEXT_CHANGE) {
- this.root.classList.toggle('ql-blank', this.editor.isBlank());
- }
- });
- this.emitter.on(Emitter.events.SCROLL_UPDATE, (source, mutations) => {
- const oldRange = this.selection.lastRange;
- const [newRange] = this.selection.getRange();
- const selectionInfo = oldRange && newRange ? {
- oldRange,
- newRange
- } : undefined;
- modify.call(this, () => this.editor.update(null, mutations, selectionInfo), source);
- });
- this.emitter.on(Emitter.events.SCROLL_EMBED_UPDATE, (blot, delta) => {
- const oldRange = this.selection.lastRange;
- const [newRange] = this.selection.getRange();
- const selectionInfo = oldRange && newRange ? {
- oldRange,
- newRange
- } : undefined;
- modify.call(this, () => {
- const change = new Delta().retain(blot.offset(this)).retain({
- [blot.statics.blotName]: delta
- });
- return this.editor.update(change, [], selectionInfo);
- }, Quill.sources.USER);
- });
- if (html) {
- const contents = this.clipboard.convert({
- html: `${html}<p><br></p>`,
- text: '\n'
- });
- this.setContents(contents);
- }
- this.history.clear();
- if (this.options.placeholder) {
- this.root.setAttribute('data-placeholder', this.options.placeholder);
- }
- if (this.options.readOnly) {
- this.disable();
- }
- this.allowReadOnlyEdits = false;
- }
- addContainer(container) {
- let refNode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
- if (typeof container === 'string') {
- const className = container;
- container = document.createElement('div');
- container.classList.add(className);
- }
- this.container.insertBefore(container, refNode);
- return container;
- }
- blur() {
- this.selection.setRange(null);
- }
- deleteText(index, length, source) {
- // @ts-expect-error
- [index, length,, source] = overload(index, length, source);
- return modify.call(this, () => {
- return this.editor.deleteText(index, length);
- }, source, index, -1 * length);
- }
- disable() {
- this.enable(false);
- }
- editReadOnly(modifier) {
- this.allowReadOnlyEdits = true;
- const value = modifier();
- this.allowReadOnlyEdits = false;
- return value;
- }
- enable() {
- let enabled = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
- this.scroll.enable(enabled);
- this.container.classList.toggle('ql-disabled', !enabled);
- }
- focus() {
- let options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
- this.selection.focus();
- if (!options.preventScroll) {
- this.scrollSelectionIntoView();
- }
- }
- format(name, value) {
- let source = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Emitter.sources.API;
- return modify.call(this, () => {
- const range = this.getSelection(true);
- let change = new Delta();
- if (range == null) return change;
- if (this.scroll.query(name, Parchment.Scope.BLOCK)) {
- change = this.editor.formatLine(range.index, range.length, {
- [name]: value
- });
- } else if (range.length === 0) {
- this.selection.format(name, value);
- return change;
- } else {
- change = this.editor.formatText(range.index, range.length, {
- [name]: value
- });
- }
- this.setSelection(range, Emitter.sources.SILENT);
- return change;
- }, source);
- }
- formatLine(index, length, name, value, source) {
- let formats;
- // eslint-disable-next-line prefer-const
- [index, length, formats, source] = overload(index, length,
- // @ts-expect-error
- name, value, source);
- return modify.call(this, () => {
- return this.editor.formatLine(index, length, formats);
- }, source, index, 0);
- }
- formatText(index, length, name, value, source) {
- let formats;
- // eslint-disable-next-line prefer-const
- [index, length, formats, source] = overload(
- // @ts-expect-error
- index, length, name, value, source);
- return modify.call(this, () => {
- return this.editor.formatText(index, length, formats);
- }, source, index, 0);
- }
- getBounds(index) {
- let length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
- let bounds = null;
- if (typeof index === 'number') {
- bounds = this.selection.getBounds(index, length);
- } else {
- bounds = this.selection.getBounds(index.index, index.length);
- }
- if (!bounds) return null;
- const containerBounds = this.container.getBoundingClientRect();
- return {
- bottom: bounds.bottom - containerBounds.top,
- height: bounds.height,
- left: bounds.left - containerBounds.left,
- right: bounds.right - containerBounds.left,
- top: bounds.top - containerBounds.top,
- width: bounds.width
- };
- }
- getContents() {
- let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
- let length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.getLength() - index;
- [index, length] = overload(index, length);
- return this.editor.getContents(index, length);
- }
- getFormat() {
- let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getSelection(true);
- let length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
- if (typeof index === 'number') {
- return this.editor.getFormat(index, length);
- }
- return this.editor.getFormat(index.index, index.length);
- }
- getIndex(blot) {
- return blot.offset(this.scroll);
- }
- getLength() {
- return this.scroll.length();
- }
- getLeaf(index) {
- return this.scroll.leaf(index);
- }
- getLine(index) {
- return this.scroll.line(index);
- }
- getLines() {
- let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
- let length = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Number.MAX_VALUE;
- if (typeof index !== 'number') {
- return this.scroll.lines(index.index, index.length);
- }
- return this.scroll.lines(index, length);
- }
- getModule(name) {
- return this.theme.modules[name];
- }
- getSelection() {
- let focus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
- if (focus) this.focus();
- this.update(); // Make sure we access getRange with editor in consistent state
- return this.selection.getRange()[0];
- }
- getSemanticHTML() {
- let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
- let length = arguments.length > 1 ? arguments[1] : undefined;
- if (typeof index === 'number') {
- length = length ?? this.getLength() - index;
- }
- // @ts-expect-error
- [index, length] = overload(index, length);
- return this.editor.getHTML(index, length);
- }
- getText() {
- let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
- let length = arguments.length > 1 ? arguments[1] : undefined;
- if (typeof index === 'number') {
- length = length ?? this.getLength() - index;
- }
- // @ts-expect-error
- [index, length] = overload(index, length);
- return this.editor.getText(index, length);
- }
- hasFocus() {
- return this.selection.hasFocus();
- }
- insertEmbed(index, embed, value) {
- let source = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Quill.sources.API;
- return modify.call(this, () => {
- return this.editor.insertEmbed(index, embed, value);
- }, source, index);
- }
- insertText(index, text, name, value, source) {
- let formats;
- // eslint-disable-next-line prefer-const
- // @ts-expect-error
- [index,, formats, source] = overload(index, 0, name, value, source);
- return modify.call(this, () => {
- return this.editor.insertText(index, text, formats);
- }, source, index, text.length);
- }
- isEnabled() {
- return this.scroll.isEnabled();
- }
- off() {
- return this.emitter.off(...arguments);
- }
- on() {
- return this.emitter.on(...arguments);
- }
- once() {
- return this.emitter.once(...arguments);
- }
- removeFormat(index, length, source) {
- [index, length,, source] = overload(index, length, source);
- return modify.call(this, () => {
- return this.editor.removeFormat(index, length);
- }, source, index);
- }
- scrollRectIntoView(rect) {
- scrollRectIntoView(this.root, rect);
- }
- /**
- * @deprecated Use Quill#scrollSelectionIntoView() instead.
- */
- scrollIntoView() {
- console.warn('Quill#scrollIntoView() has been deprecated and will be removed in the near future. Please use Quill#scrollSelectionIntoView() instead.');
- this.scrollSelectionIntoView();
- }
- /**
- * Scroll the current selection into the visible area.
- * If the selection is already visible, no scrolling will occur.
- */
- scrollSelectionIntoView() {
- const range = this.selection.lastRange;
- const bounds = range && this.selection.getBounds(range.index, range.length);
- if (bounds) {
- this.scrollRectIntoView(bounds);
- }
- }
- setContents(delta) {
- let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Emitter.sources.API;
- return modify.call(this, () => {
- delta = new Delta(delta);
- const length = this.getLength();
- // Quill will set empty editor to \n
- const delete1 = this.editor.deleteText(0, length);
- const applied = this.editor.insertContents(0, delta);
- // Remove extra \n from empty editor initialization
- const delete2 = this.editor.deleteText(this.getLength() - 1, 1);
- return delete1.compose(applied).compose(delete2);
- }, source);
- }
- setSelection(index, length, source) {
- if (index == null) {
- // @ts-expect-error https://github.com/microsoft/TypeScript/issues/22609
- this.selection.setRange(null, length || Quill.sources.API);
- } else {
- // @ts-expect-error
- [index, length,, source] = overload(index, length, source);
- this.selection.setRange(new Range(Math.max(0, index), length), source);
- if (source !== Emitter.sources.SILENT) {
- this.scrollSelectionIntoView();
- }
- }
- }
- setText(text) {
- let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Emitter.sources.API;
- const delta = new Delta().insert(text);
- return this.setContents(delta, source);
- }
- update() {
- let source = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Emitter.sources.USER;
- const change = this.scroll.update(source); // Will update selection before selection.update() does if text changes
- this.selection.update(source);
- // TODO this is usually undefined
- return change;
- }
- updateContents(delta) {
- let source = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Emitter.sources.API;
- return modify.call(this, () => {
- delta = new Delta(delta);
- return this.editor.applyDelta(delta);
- }, source, true);
- }
- }
- function resolveSelector(selector) {
- return typeof selector === 'string' ? document.querySelector(selector) : selector;
- }
- function expandModuleConfig(config) {
- return Object.entries(config ?? {}).reduce((expanded, _ref) => {
- let [key, value] = _ref;
- return {
- ...expanded,
- [key]: value === true ? {} : value
- };
- }, {});
- }
- function omitUndefinedValuesFromOptions(obj) {
- return Object.fromEntries(Object.entries(obj).filter(entry => entry[1] !== undefined));
- }
- function expandConfig(containerOrSelector, options) {
- const container = resolveSelector(containerOrSelector);
- if (!container) {
- throw new Error('Invalid Quill container');
- }
- const shouldUseDefaultTheme = !options.theme || options.theme === Quill.DEFAULTS.theme;
- const theme = shouldUseDefaultTheme ? Theme : Quill.import(`themes/${options.theme}`);
- if (!theme) {
- throw new Error(`Invalid theme ${options.theme}. Did you register it?`);
- }
- const {
- modules: quillModuleDefaults,
- ...quillDefaults
- } = Quill.DEFAULTS;
- const {
- modules: themeModuleDefaults,
- ...themeDefaults
- } = theme.DEFAULTS;
- let userModuleOptions = expandModuleConfig(options.modules);
- // Special case toolbar shorthand
- if (userModuleOptions != null && userModuleOptions.toolbar && userModuleOptions.toolbar.constructor !== Object) {
- userModuleOptions = {
- ...userModuleOptions,
- toolbar: {
- container: userModuleOptions.toolbar
- }
- };
- }
- const modules = merge({}, expandModuleConfig(quillModuleDefaults), expandModuleConfig(themeModuleDefaults), userModuleOptions);
- const config = {
- ...quillDefaults,
- ...omitUndefinedValuesFromOptions(themeDefaults),
- ...omitUndefinedValuesFromOptions(options)
- };
- let registry = options.registry;
- if (registry) {
- if (options.formats) {
- debug.warn('Ignoring "formats" option because "registry" is specified');
- }
- } else {
- registry = options.formats ? createRegistryWithFormats(options.formats, config.registry, debug) : config.registry;
- }
- return {
- ...config,
- registry,
- container,
- theme,
- modules: Object.entries(modules).reduce((modulesWithDefaults, _ref2) => {
- let [name, value] = _ref2;
- if (!value) return modulesWithDefaults;
- const moduleClass = Quill.import(`modules/${name}`);
- if (moduleClass == null) {
- debug.error(`Cannot load ${name} module. Are you sure you registered it?`);
- return modulesWithDefaults;
- }
- return {
- ...modulesWithDefaults,
- // @ts-expect-error
- [name]: merge({}, moduleClass.DEFAULTS || {}, value)
- };
- }, {}),
- bounds: resolveSelector(config.bounds)
- };
- }
- // Handle selection preservation and TEXT_CHANGE emission
- // common to modification APIs
- function modify(modifier, source, index, shift) {
- if (!this.isEnabled() && source === Emitter.sources.USER && !this.allowReadOnlyEdits) {
- return new Delta();
- }
- let range = index == null ? null : this.getSelection();
- const oldDelta = this.editor.delta;
- const change = modifier();
- if (range != null) {
- if (index === true) {
- index = range.index; // eslint-disable-line prefer-destructuring
- }
- if (shift == null) {
- range = shiftRange(range, change, source);
- } else if (shift !== 0) {
- // @ts-expect-error index should always be number
- range = shiftRange(range, index, shift, source);
- }
- this.setSelection(range, Emitter.sources.SILENT);
- }
- if (change.length() > 0) {
- const args = [Emitter.events.TEXT_CHANGE, change, oldDelta, source];
- this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
- if (source !== Emitter.sources.SILENT) {
- this.emitter.emit(...args);
- }
- }
- return change;
- }
- function overload(index, length, name, value, source) {
- let formats = {};
- // @ts-expect-error
- if (typeof index.index === 'number' && typeof index.length === 'number') {
- // Allow for throwaway end (used by insertText/insertEmbed)
- if (typeof length !== 'number') {
- // @ts-expect-error
- source = value;
- value = name;
- name = length;
- // @ts-expect-error
- length = index.length; // eslint-disable-line prefer-destructuring
- // @ts-expect-error
- index = index.index; // eslint-disable-line prefer-destructuring
- } else {
- // @ts-expect-error
- length = index.length; // eslint-disable-line prefer-destructuring
- // @ts-expect-error
- index = index.index; // eslint-disable-line prefer-destructuring
- }
- } else if (typeof length !== 'number') {
- // @ts-expect-error
- source = value;
- value = name;
- name = length;
- length = 0;
- }
- // Handle format being object, two format name/value strings or excluded
- if (typeof name === 'object') {
- // @ts-expect-error Fix me later
- formats = name;
- // @ts-expect-error
- source = value;
- } else if (typeof name === 'string') {
- if (value != null) {
- formats[name] = value;
- } else {
- // @ts-expect-error
- source = name;
- }
- }
- // Handle optional source
- source = source || Emitter.sources.API;
- // @ts-expect-error
- return [index, length, formats, source];
- }
- function shiftRange(range, index, lengthOrSource, source) {
- const length = typeof lengthOrSource === 'number' ? lengthOrSource : 0;
- if (range == null) return null;
- let start;
- let end;
- // @ts-expect-error -- TODO: add a better type guard around `index`
- if (index && typeof index.transformPosition === 'function') {
- [start, end] = [range.index, range.index + range.length].map(pos =>
- // @ts-expect-error -- TODO: add a better type guard around `index`
- index.transformPosition(pos, source !== Emitter.sources.USER));
- } else {
- [start, end] = [range.index, range.index + range.length].map(pos => {
- // @ts-expect-error -- TODO: add a better type guard around `index`
- if (pos < index || pos === index && source === Emitter.sources.USER) return pos;
- if (length >= 0) {
- return pos + length;
- }
- // @ts-expect-error -- TODO: add a better type guard around `index`
- return Math.max(index, pos + length);
- });
- }
- return new Range(start, end - start);
- }
- export { Parchment, Range };
- export { globalRegistry, expandConfig, overload, Quill as default };
- //# sourceMappingURL=quill.js.map
|