Archived
1
0

Initial commit as of 2018-10-16

This commit is contained in:
Marcel
2018-10-16 18:28:42 +02:00
commit 29d7c2ffdc
3601 changed files with 358427 additions and 0 deletions

267
assets/js/core/editor.js Normal file
View File

@@ -0,0 +1,267 @@
import Delta from 'quill-delta';
import DeltaOp from 'quill-delta/lib/op';
import Parchment from 'parchment';
import CodeBlock from '../formats/code';
import CursorBlot from '../blots/cursor';
import Block, { bubbleFormats } from '../blots/block';
import Break from '../blots/break';
import clone from 'clone';
import equal from 'deep-equal';
import extend from 'extend';
const ASCII = /^[ -~]*$/;
class Editor {
constructor(scroll) {
this.scroll = scroll;
this.delta = this.getDelta();
}
applyDelta(delta) {
let consumeNextNewline = false;
this.scroll.update();
let scrollLength = this.scroll.length();
this.scroll.batchStart();
delta = normalizeDelta(delta);
delta.reduce((index, op) => {
let length = op.retain || op.delete || op.insert.length || 1;
let attributes = op.attributes || {};
if (op.insert != null) {
if (typeof op.insert === 'string') {
let text = op.insert;
if (text.endsWith('\n') && consumeNextNewline) {
consumeNextNewline = false;
text = text.slice(0, -1);
}
if (index >= scrollLength && !text.endsWith('\n')) {
consumeNextNewline = true;
}
this.scroll.insertAt(index, text);
let [line, offset] = this.scroll.line(index);
let formats = extend({}, bubbleFormats(line));
if (line instanceof Block) {
let [leaf, ] = line.descendant(Parchment.Leaf, offset);
formats = extend(formats, bubbleFormats(leaf));
}
attributes = DeltaOp.attributes.diff(formats, attributes) || {};
} else if (typeof op.insert === 'object') {
let key = Object.keys(op.insert)[0]; // There should only be one key
if (key == null) return index;
this.scroll.insertAt(index, key, op.insert[key]);
}
scrollLength += length;
}
Object.keys(attributes).forEach((name) => {
this.scroll.formatAt(index, length, name, attributes[name]);
});
return index + length;
}, 0);
delta.reduce((index, op) => {
if (typeof op.delete === 'number') {
this.scroll.deleteAt(index, op.delete);
return index;
}
return index + (op.retain || op.insert.length || 1);
}, 0);
this.scroll.batchEnd();
return this.update(delta);
}
deleteText(index, length) {
this.scroll.deleteAt(index, length);
return this.update(new Delta().retain(index).delete(length));
}
formatLine(index, length, formats = {}) {
this.scroll.update();
Object.keys(formats).forEach((format) => {
if (this.scroll.whitelist != null && !this.scroll.whitelist[format]) return;
let lines = this.scroll.lines(index, Math.max(length, 1));
let lengthRemaining = length;
lines.forEach((line) => {
let lineLength = line.length();
if (!(line instanceof CodeBlock)) {
line.format(format, formats[format]);
} else {
let codeIndex = index - line.offset(this.scroll);
let codeLength = line.newlineIndex(codeIndex + lengthRemaining) - codeIndex + 1;
line.formatAt(codeIndex, codeLength, format, formats[format]);
}
lengthRemaining -= lineLength;
});
});
this.scroll.optimize();
return this.update(new Delta().retain(index).retain(length, clone(formats)));
}
formatText(index, length, formats = {}) {
Object.keys(formats).forEach((format) => {
this.scroll.formatAt(index, length, format, formats[format]);
});
return this.update(new Delta().retain(index).retain(length, clone(formats)));
}
getContents(index, length) {
return this.delta.slice(index, index + length);
}
getDelta() {
return this.scroll.lines().reduce((delta, line) => {
return delta.concat(line.delta());
}, new Delta());
}
getFormat(index, length = 0) {
let lines = [], leaves = [];
if (length === 0) {
this.scroll.path(index).forEach(function(path) {
let [blot, ] = path;
if (blot instanceof Block) {
lines.push(blot);
} else if (blot instanceof Parchment.Leaf) {
leaves.push(blot);
}
});
} else {
lines = this.scroll.lines(index, length);
leaves = this.scroll.descendants(Parchment.Leaf, index, length);
}
let formatsArr = [lines, leaves].map(function(blots) {
if (blots.length === 0) return {};
let formats = bubbleFormats(blots.shift());
while (Object.keys(formats).length > 0) {
let blot = blots.shift();
if (blot == null) return formats;
formats = combineFormats(bubbleFormats(blot), formats);
}
return formats;
});
return extend.apply(extend, formatsArr);
}
getText(index, length) {
return this.getContents(index, length).filter(function(op) {
return typeof op.insert === 'string';
}).map(function(op) {
return op.insert;
}).join('');
}
insertEmbed(index, embed, value) {
this.scroll.insertAt(index, embed, value);
return this.update(new Delta().retain(index).insert({ [embed]: value }));
}
insertText(index, text, formats = {}) {
text = text.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
this.scroll.insertAt(index, text);
Object.keys(formats).forEach((format) => {
this.scroll.formatAt(index, text.length, format, formats[format]);
});
return this.update(new Delta().retain(index).insert(text, clone(formats)));
}
isBlank() {
if (this.scroll.children.length == 0) return true;
if (this.scroll.children.length > 1) return false;
let block = this.scroll.children.head;
if (block.statics.blotName !== Block.blotName) return false;
if (block.children.length > 1) return false;
return block.children.head instanceof Break;
}
removeFormat(index, length) {
let text = this.getText(index, length);
let [line, offset] = this.scroll.line(index + length);
let suffixLength = 0, suffix = new Delta();
if (line != null) {
if (!(line instanceof CodeBlock)) {
suffixLength = line.length() - offset;
} else {
suffixLength = line.newlineIndex(offset) - offset + 1;
}
suffix = line.delta().slice(offset, offset + suffixLength - 1).insert('\n');
}
let contents = this.getContents(index, length + suffixLength);
let diff = contents.diff(new Delta().insert(text).concat(suffix));
let delta = new Delta().retain(index).concat(diff);
return this.applyDelta(delta);
}
update(change, mutations = [], cursorIndex = undefined) {
let oldDelta = this.delta;
if (mutations.length === 1 &&
mutations[0].type === 'characterData' &&
mutations[0].target.data.match(ASCII) &&
Parchment.find(mutations[0].target)) {
// Optimization for character changes
let textBlot = Parchment.find(mutations[0].target);
let formats = bubbleFormats(textBlot);
let index = textBlot.offset(this.scroll);
let oldValue = mutations[0].oldValue.replace(CursorBlot.CONTENTS, '');
let oldText = new Delta().insert(oldValue);
let newText = new Delta().insert(textBlot.value());
let diffDelta = new Delta().retain(index).concat(oldText.diff(newText, cursorIndex));
change = diffDelta.reduce(function(delta, op) {
if (op.insert) {
return delta.insert(op.insert, formats);
} else {
return delta.push(op);
}
}, new Delta());
this.delta = oldDelta.compose(change);
} else {
this.delta = this.getDelta();
if (!change || !equal(oldDelta.compose(change), this.delta)) {
change = oldDelta.diff(this.delta, cursorIndex);
}
}
return change;
}
}
function combineFormats(formats, combined) {
return Object.keys(combined).reduce(function(merged, name) {
if (formats[name] == null) return merged;
if (combined[name] === formats[name]) {
merged[name] = combined[name];
} else if (Array.isArray(combined[name])) {
if (combined[name].indexOf(formats[name]) < 0) {
merged[name] = combined[name].concat([formats[name]]);
}
} else {
merged[name] = [combined[name], formats[name]];
}
return merged;
}, {});
}
function normalizeDelta(delta) {
return delta.reduce(function(delta, op) {
if (op.insert === 1) {
let attributes = clone(op.attributes);
delete attributes['image'];
return delta.insert({ image: op.attributes.image }, attributes);
}
if (op.attributes != null && (op.attributes.list === true || op.attributes.bullet === true)) {
op = clone(op);
if (op.attributes.list) {
op.attributes.list = 'ordered';
} else {
op.attributes.list = 'bullet';
delete op.attributes.bullet;
}
}
if (typeof op.insert === 'string') {
let text = op.insert.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
return delta.insert(text, op.attributes);
}
return delta.push(op);
}, new Delta());
}
export default Editor;

63
assets/js/core/emitter.js Normal file
View File

@@ -0,0 +1,63 @@
import EventEmitter from 'eventemitter3';
import logger from './logger';
let debug = logger('quill:events');
const EVENTS = ['selectionchange', 'mousedown', 'mouseup', 'click'];
EVENTS.forEach(function(eventName) {
document.addEventListener(eventName, (...args) => {
[].slice.call(document.querySelectorAll('.ql-container')).forEach((node) => {
// TODO use WeakMap
if (node.__quill && node.__quill.emitter) {
node.__quill.emitter.handleDOM(...args);
}
});
});
});
class Emitter extends EventEmitter {
constructor() {
super();
this.listeners = {};
this.on('error', debug.error);
}
emit() {
debug.log.apply(debug, arguments);
super.emit.apply(this, arguments);
}
handleDOM(event, ...args) {
(this.listeners[event.type] || []).forEach(function({ node, handler }) {
if (event.target === node || node.contains(event.target)) {
handler(event, ...args);
}
});
}
listenDOM(eventName, node, handler) {
if (!this.listeners[eventName]) {
this.listeners[eventName] = [];
}
this.listeners[eventName].push({ node, handler })
}
}
Emitter.events = {
EDITOR_CHANGE : 'editor-change',
SCROLL_BEFORE_UPDATE : 'scroll-before-update',
SCROLL_OPTIMIZE : 'scroll-optimize',
SCROLL_UPDATE : 'scroll-update',
SELECTION_CHANGE : 'selection-change',
TEXT_CHANGE : 'text-change'
};
Emitter.sources = {
API : 'api',
SILENT : 'silent',
USER : 'user'
};
export default Emitter;

22
assets/js/core/logger.js Normal file
View File

@@ -0,0 +1,22 @@
let levels = ['error', 'warn', 'log', 'info'];
let level = 'warn';
function debug(method, ...args) {
if (levels.indexOf(method) <= levels.indexOf(level)) {
console[method](...args); // eslint-disable-line no-console
}
}
function namespace(ns) {
return levels.reduce(function(logger, method) {
logger[method] = debug.bind(console, method, ns);
return logger;
}, {});
}
debug.level = namespace.level = function(newLevel) {
level = newLevel;
};
export default namespace;

10
assets/js/core/module.js Normal file
View File

@@ -0,0 +1,10 @@
class Module {
constructor(quill, options = {}) {
this.quill = quill;
this.options = options;
}
}
Module.DEFAULTS = {};
export default Module;

View File

@@ -0,0 +1,63 @@
let elem = document.createElement('div');
elem.classList.toggle('test-class', false);
if (elem.classList.contains('test-class')) {
let _toggle = DOMTokenList.prototype.toggle;
DOMTokenList.prototype.toggle = function(token, force) {
if (arguments.length > 1 && !this.contains(token) === !force) {
return force;
} else {
return _toggle.call(this, token);
}
};
}
if (!String.prototype.startsWith) {
String.prototype.startsWith = function(searchString, position){
position = position || 0;
return this.substr(position, searchString.length) === searchString;
};
}
if (!String.prototype.endsWith) {
String.prototype.endsWith = function(searchString, position) {
var subjectString = this.toString();
if (typeof position !== 'number' || !isFinite(position) || Math.floor(position) !== position || position > subjectString.length) {
position = subjectString.length;
}
position -= searchString.length;
var lastIndex = subjectString.indexOf(searchString, position);
return lastIndex !== -1 && lastIndex === position;
};
}
if (!Array.prototype.find) {
Object.defineProperty(Array.prototype, "find", {
value: function(predicate) {
if (this === null) {
throw new TypeError('Array.prototype.find called on null or undefined');
}
if (typeof predicate !== 'function') {
throw new TypeError('predicate must be a function');
}
var list = Object(this);
var length = list.length >>> 0;
var thisArg = arguments[1];
var value;
for (var i = 0; i < length; i++) {
value = list[i];
if (predicate.call(thisArg, value, i, list)) {
return value;
}
}
return undefined;
}
});
}
document.addEventListener("DOMContentLoaded", function() {
// Disable resizing in Firefox
document.execCommand("enableObjectResizing", false, false);
// Disable automatic linkifying in IE11
document.execCommand("autoUrlDetect", false, false);
});

506
assets/js/core/quill.js Normal file
View File

@@ -0,0 +1,506 @@
import './polyfill';
import Delta from 'quill-delta';
import Editor from './editor';
import Emitter from './emitter';
import Module from './module';
import Parchment from 'parchment';
import Selection, { Range } from './selection';
import extend from 'extend';
import logger from './logger';
import Theme from './theme';
let debug = logger('quill');
class Quill {
static debug(limit) {
if (limit === true) {
limit = 'log';
}
logger.level(limit);
}
static find(node) {
return node.__quill || Parchment.find(node);
}
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(path, target, overwrite = false) {
if (typeof path !== 'string') {
let name = path.attrName || path.blotName;
if (typeof name === 'string') {
// register(Blot | Attributor, overwrite)
this.register('formats/' + name, path, target);
} else {
Object.keys(path).forEach((key) => {
this.register(key, path[key], target);
});
}
} else {
if (this.imports[path] != null && !overwrite) {
debug.warn(`Overwriting ${path} with`, target);
}
this.imports[path] = target;
if ((path.startsWith('blots/') || path.startsWith('formats/')) &&
target.blotName !== 'abstract') {
Parchment.register(target);
} else if (path.startsWith('modules') && typeof target.register === 'function') {
target.register();
}
}
}
constructor(container, options = {}) {
this.options = expandConfig(container, options);
this.container = this.options.container;
if (this.container == null) {
return debug.error('Invalid Quill container', container);
}
if (this.options.debug) {
Quill.debug(this.options.debug);
}
let html = this.container.innerHTML.trim();
this.container.classList.add('ql-container');
this.container.innerHTML = '';
this.container.__quill = this;
this.root = this.addContainer('ql-editor');
this.root.classList.add('ql-blank');
this.root.setAttribute('data-gramm', false);
this.scrollingContainer = this.options.scrollingContainer || this.root;
this.emitter = new Emitter();
this.scroll = Parchment.create(this.root, {
emitter: this.emitter,
whitelist: this.options.formats
});
this.editor = new Editor(this.scroll);
this.selection = new Selection(this.scroll, this.emitter);
this.theme = new this.options.theme(this, this.options);
this.keyboard = this.theme.addModule('keyboard');
this.clipboard = this.theme.addModule('clipboard');
this.history = this.theme.addModule('history');
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) => {
let range = this.selection.lastRange;
let index = range && range.length === 0 ? range.index : undefined;
modify.call(this, () => {
return this.editor.update(null, mutations, index);
}, source);
});
let contents = this.clipboard.convert(`<div class='ql-editor' style="white-space: normal;">${html}<p><br></p></div>`);
this.setContents(contents);
this.history.clear();
if (this.options.placeholder) {
this.root.setAttribute('data-placeholder', this.options.placeholder);
}
if (this.options.readOnly) {
this.disable();
}
}
addContainer(container, refNode = null) {
if (typeof container === 'string') {
let 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) {
[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);
}
enable(enabled = true) {
this.scroll.enable(enabled);
this.container.classList.toggle('ql-disabled', !enabled);
}
focus() {
let scrollTop = this.scrollingContainer.scrollTop;
this.selection.focus();
this.scrollingContainer.scrollTop = scrollTop;
this.scrollIntoView();
}
format(name, value, source = Emitter.sources.API) {
return modify.call(this, () => {
let range = this.getSelection(true);
let change = new Delta();
if (range == null) {
return change;
} else if (Parchment.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;
[index, length, formats, source] = overload(index, length, 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;
[index, length, formats, source] = overload(index, length, name, value, source);
return modify.call(this, () => {
return this.editor.formatText(index, length, formats);
}, source, index, 0);
}
getBounds(index, length = 0) {
let bounds;
if (typeof index === 'number') {
bounds = this.selection.getBounds(index, length);
} else {
bounds = this.selection.getBounds(index.index, index.length);
}
let 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(index = 0, length = this.getLength() - index) {
[index, length] = overload(index, length);
return this.editor.getContents(index, length);
}
getFormat(index = this.getSelection(true), length = 0) {
if (typeof index === 'number') {
return this.editor.getFormat(index, length);
} else {
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(index = 0, length = Number.MAX_VALUE) {
if (typeof index !== 'number') {
return this.scroll.lines(index.index, index.length);
} else {
return this.scroll.lines(index, length);
}
}
getModule(name) {
return this.theme.modules[name];
}
getSelection(focus = false) {
if (focus) this.focus();
this.update(); // Make sure we access getRange with editor in consistent state
return this.selection.getRange()[0];
}
getText(index = 0, length = this.getLength() - index) {
[index, length] = overload(index, length);
return this.editor.getText(index, length);
}
hasFocus() {
return this.selection.hasFocus();
}
insertEmbed(index, embed, value, source = Quill.sources.API) {
return modify.call(this, () => {
return this.editor.insertEmbed(index, embed, value);
}, source, index);
}
insertText(index, text, name, value, source) {
let formats;
[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.container.classList.contains('ql-disabled');
}
off() {
return this.emitter.off.apply(this.emitter, arguments);
}
on() {
return this.emitter.on.apply(this.emitter, arguments);
}
once() {
return this.emitter.once.apply(this.emitter, arguments);
}
pasteHTML(index, html, source) {
this.clipboard.dangerouslyPasteHTML(index, html, source);
}
removeFormat(index, length, source) {
[index, length, , source] = overload(index, length, source);
return modify.call(this, () => {
return this.editor.removeFormat(index, length);
}, source, index);
}
scrollIntoView() {
this.selection.scrollIntoView(this.scrollingContainer);
}
setContents(delta, source = Emitter.sources.API) {
return modify.call(this, () => {
delta = new Delta(delta);
let length = this.getLength();
let deleted = this.editor.deleteText(0, length);
let applied = this.editor.applyDelta(delta);
let lastOp = applied.ops[applied.ops.length - 1];
if (lastOp != null && typeof(lastOp.insert) === 'string' && lastOp.insert[lastOp.insert.length-1] === '\n') {
this.editor.deleteText(this.getLength() - 1, 1);
applied.delete(1);
}
let ret = deleted.compose(applied);
return ret;
}, source);
}
setSelection(index, length, source) {
if (index == null) {
this.selection.setRange(null, length || Quill.sources.API);
} else {
[index, length, , source] = overload(index, length, source);
this.selection.setRange(new Range(index, length), source);
if (source !== Emitter.sources.SILENT) {
this.selection.scrollIntoView(this.scrollingContainer);
}
}
}
setText(text, source = Emitter.sources.API) {
let delta = new Delta().insert(text);
return this.setContents(delta, source);
}
update(source = Emitter.sources.USER) {
let change = this.scroll.update(source); // Will update selection before selection.update() does if text changes
this.selection.update(source);
return change;
}
updateContents(delta, source = Emitter.sources.API) {
return modify.call(this, () => {
delta = new Delta(delta);
return this.editor.applyDelta(delta, source);
}, source, true);
}
}
Quill.DEFAULTS = {
bounds: null,
formats: null,
modules: {},
placeholder: '',
readOnly: false,
scrollingContainer: null,
strict: true,
theme: 'default'
};
Quill.events = Emitter.events;
Quill.sources = Emitter.sources;
// eslint-disable-next-line no-undef
Quill.version = typeof(QUILL_VERSION) === 'undefined' ? 'dev' : QUILL_VERSION;
Quill.imports = {
'delta' : Delta,
'parchment' : Parchment,
'core/module' : Module,
'core/theme' : Theme
};
function expandConfig(container, userConfig) {
userConfig = extend(true, {
container: container,
modules: {
clipboard: true,
keyboard: true,
history: true
}
}, userConfig);
if (!userConfig.theme || userConfig.theme === Quill.DEFAULTS.theme) {
userConfig.theme = Theme;
} else {
userConfig.theme = Quill.import(`themes/${userConfig.theme}`);
if (userConfig.theme == null) {
throw new Error(`Invalid theme ${userConfig.theme}. Did you register it?`);
}
}
let themeConfig = extend(true, {}, userConfig.theme.DEFAULTS);
[themeConfig, userConfig].forEach(function(config) {
config.modules = config.modules || {};
Object.keys(config.modules).forEach(function(module) {
if (config.modules[module] === true) {
config.modules[module] = {};
}
});
});
let moduleNames = Object.keys(themeConfig.modules).concat(Object.keys(userConfig.modules));
let moduleConfig = moduleNames.reduce(function(config, name) {
let moduleClass = Quill.import(`modules/${name}`);
if (moduleClass == null) {
debug.error(`Cannot load ${name} module. Are you sure you registered it?`);
} else {
config[name] = moduleClass.DEFAULTS || {};
}
return config;
}, {});
// Special case toolbar shorthand
if (userConfig.modules != null && userConfig.modules.toolbar &&
userConfig.modules.toolbar.constructor !== Object) {
userConfig.modules.toolbar = {
container: userConfig.modules.toolbar
};
}
userConfig = extend(true, {}, Quill.DEFAULTS, { modules: moduleConfig }, themeConfig, userConfig);
['bounds', 'container', 'scrollingContainer'].forEach(function(key) {
if (typeof userConfig[key] === 'string') {
userConfig[key] = document.querySelector(userConfig[key]);
}
});
userConfig.modules = Object.keys(userConfig.modules).reduce(function(config, name) {
if (userConfig.modules[name]) {
config[name] = userConfig.modules[name];
}
return config;
}, {});
return userConfig;
}
// Handle selection preservation and TEXT_CHANGE emission
// common to modification APIs
function modify(modifier, source, index, shift) {
if (this.options.strict && !this.isEnabled() && source === Emitter.sources.USER) {
return new Delta();
}
let range = index == null ? null : this.getSelection();
let oldDelta = this.editor.delta;
let change = modifier();
if (range != null) {
if (index === true) index = range.index;
if (shift == null) {
range = shiftRange(range, change, source);
} else if (shift !== 0) {
range = shiftRange(range, index, shift, source);
}
this.setSelection(range, Emitter.sources.SILENT);
}
if (change.length() > 0) {
let 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 = {};
if (typeof index.index === 'number' && typeof index.length === 'number') {
// Allow for throwaway end (used by insertText/insertEmbed)
if (typeof length !== 'number') {
source = value, value = name, name = length, length = index.length, index = index.index;
} else {
length = index.length, index = index.index;
}
} else if (typeof length !== 'number') {
source = value, value = name, name = length, length = 0;
}
// Handle format being object, two format name/value strings or excluded
if (typeof name === 'object') {
formats = name;
source = value;
} else if (typeof name === 'string') {
if (value != null) {
formats[name] = value;
} else {
source = name;
}
}
// Handle optional source
source = source || Emitter.sources.API;
return [index, length, formats, source];
}
function shiftRange(range, index, length, source) {
if (range == null) return null;
let start, end;
if (index instanceof Delta) {
[start, end] = [range.index, range.index + range.length].map(function(pos) {
return index.transformPosition(pos, source !== Emitter.sources.USER);
});
} else {
[start, end] = [range.index, range.index + range.length].map(function(pos) {
if (pos < index || (pos === index && source === Emitter.sources.USER)) return pos;
if (length >= 0) {
return pos + length;
} else {
return Math.max(index, pos + length);
}
});
}
return new Range(start, end - start);
}
export { expandConfig, overload, Quill as default };

355
assets/js/core/selection.js Normal file
View File

@@ -0,0 +1,355 @@
import Parchment from 'parchment';
import clone from 'clone';
import equal from 'deep-equal';
import Emitter from './emitter';
import logger from './logger';
let debug = logger('quill:selection');
class Range {
constructor(index, length = 0) {
this.index = index;
this.length = length;
}
}
class Selection {
constructor(scroll, emitter) {
this.emitter = emitter;
this.scroll = scroll;
this.composing = false;
this.mouseDown = false;
this.root = this.scroll.domNode;
this.cursor = Parchment.create('cursor', this);
// savedRange is last non-null range
this.lastRange = this.savedRange = new Range(0, 0);
this.handleComposition();
this.handleDragging();
this.emitter.listenDOM('selectionchange', document, () => {
if (!this.mouseDown) {
setTimeout(this.update.bind(this, Emitter.sources.USER), 1);
}
});
this.emitter.on(Emitter.events.EDITOR_CHANGE, (type, delta) => {
if (type === Emitter.events.TEXT_CHANGE && delta.length() > 0) {
this.update(Emitter.sources.SILENT);
}
});
this.emitter.on(Emitter.events.SCROLL_BEFORE_UPDATE, () => {
if (!this.hasFocus()) return;
let native = this.getNativeRange();
if (native == null) return;
if (native.start.node === this.cursor.textNode) return; // cursor.restore() will handle
// TODO unclear if this has negative side effects
this.emitter.once(Emitter.events.SCROLL_UPDATE, () => {
try {
this.setNativeRange(native.start.node, native.start.offset, native.end.node, native.end.offset);
} catch (ignored) {}
});
});
this.emitter.on(Emitter.events.SCROLL_OPTIMIZE, (mutations, context) => {
if (context.range) {
const { startNode, startOffset, endNode, endOffset } = context.range;
this.setNativeRange(startNode, startOffset, endNode, endOffset);
}
});
this.update(Emitter.sources.SILENT);
}
handleComposition() {
this.root.addEventListener('compositionstart', () => {
this.composing = true;
});
this.root.addEventListener('compositionend', () => {
this.composing = false;
if (this.cursor.parent) {
const range = this.cursor.restore();
if (!range) return;
setTimeout(() => {
this.setNativeRange(range.startNode, range.startOffset, range.endNode, range.endOffset);
}, 1);
}
});
}
handleDragging() {
this.emitter.listenDOM('mousedown', document.body, () => {
this.mouseDown = true;
});
this.emitter.listenDOM('mouseup', document.body, () => {
this.mouseDown = false;
this.update(Emitter.sources.USER);
});
}
focus() {
if (this.hasFocus()) return;
this.root.focus();
this.setRange(this.savedRange);
}
format(format, value) {
if (this.scroll.whitelist != null && !this.scroll.whitelist[format]) return;
this.scroll.update();
let nativeRange = this.getNativeRange();
if (nativeRange == null || !nativeRange.native.collapsed || Parchment.query(format, Parchment.Scope.BLOCK)) return;
if (nativeRange.start.node !== this.cursor.textNode) {
let blot = Parchment.find(nativeRange.start.node, false);
if (blot == null) return;
// TODO Give blot ability to not split
if (blot instanceof Parchment.Leaf) {
let after = blot.split(nativeRange.start.offset);
blot.parent.insertBefore(this.cursor, after);
} else {
blot.insertBefore(this.cursor, nativeRange.start.node); // Should never happen
}
this.cursor.attach();
}
this.cursor.format(format, value);
this.scroll.optimize();
this.setNativeRange(this.cursor.textNode, this.cursor.textNode.data.length);
this.update();
}
getBounds(index, length = 0) {
let scrollLength = this.scroll.length();
index = Math.min(index, scrollLength - 1);
length = Math.min(index + length, scrollLength - 1) - index;
let node, [leaf, offset] = this.scroll.leaf(index);
if (leaf == null) return null;
[node, offset] = leaf.position(offset, true);
let range = document.createRange();
if (length > 0) {
range.setStart(node, offset);
[leaf, offset] = this.scroll.leaf(index + length);
if (leaf == null) return null;
[node, offset] = leaf.position(offset, true);
range.setEnd(node, offset);
return range.getBoundingClientRect();
} else {
let side = 'left';
let rect;
if (node instanceof Text) {
if (offset < node.data.length) {
range.setStart(node, offset);
range.setEnd(node, offset + 1);
} else {
range.setStart(node, offset - 1);
range.setEnd(node, offset);
side = 'right';
}
rect = range.getBoundingClientRect();
} else {
rect = leaf.domNode.getBoundingClientRect();
if (offset > 0) side = 'right';
}
return {
bottom: rect.top + rect.height,
height: rect.height,
left: rect[side],
right: rect[side],
top: rect.top,
width: 0
};
}
}
getNativeRange() {
let selection = document.getSelection();
if (selection == null || selection.rangeCount <= 0) return null;
let nativeRange = selection.getRangeAt(0);
if (nativeRange == null) return null;
let range = this.normalizeNative(nativeRange);
debug.info('getNativeRange', range);
return range;
}
getRange() {
let normalized = this.getNativeRange();
if (normalized == null) return [null, null];
let range = this.normalizedToRange(normalized);
return [range, normalized];
}
hasFocus() {
return document.activeElement === this.root;
}
normalizedToRange(range) {
let positions = [[range.start.node, range.start.offset]];
if (!range.native.collapsed) {
positions.push([range.end.node, range.end.offset]);
}
let indexes = positions.map((position) => {
let [node, offset] = position;
let blot = Parchment.find(node, true);
let index = blot.offset(this.scroll);
if (offset === 0) {
return index;
} else if (blot instanceof Parchment.Container) {
return index + blot.length();
} else {
return index + blot.index(node, offset);
}
});
let end = Math.min(Math.max(...indexes), this.scroll.length() - 1);
let start = Math.min(end, ...indexes);
return new Range(start, end-start);
}
normalizeNative(nativeRange) {
if (!contains(this.root, nativeRange.startContainer) ||
(!nativeRange.collapsed && !contains(this.root, nativeRange.endContainer))) {
return null;
}
let range = {
start: { node: nativeRange.startContainer, offset: nativeRange.startOffset },
end: { node: nativeRange.endContainer, offset: nativeRange.endOffset },
native: nativeRange
};
[range.start, range.end].forEach(function(position) {
let node = position.node, offset = position.offset;
while (!(node instanceof Text) && node.childNodes.length > 0) {
if (node.childNodes.length > offset) {
node = node.childNodes[offset];
offset = 0;
} else if (node.childNodes.length === offset) {
node = node.lastChild;
offset = node instanceof Text ? node.data.length : node.childNodes.length + 1;
} else {
break;
}
}
position.node = node, position.offset = offset;
});
return range;
}
rangeToNative(range) {
let indexes = range.collapsed ? [range.index] : [range.index, range.index + range.length];
let args = [];
let scrollLength = this.scroll.length();
indexes.forEach((index, i) => {
index = Math.min(scrollLength - 1, index);
let node, [leaf, offset] = this.scroll.leaf(index);
[node, offset] = leaf.position(offset, i !== 0);
args.push(node, offset);
});
if (args.length < 2) {
args = args.concat(args);
}
return args;
}
scrollIntoView(scrollingContainer) {
let range = this.lastRange;
if (range == null) return;
let bounds = this.getBounds(range.index, range.length);
if (bounds == null) return;
let limit = this.scroll.length()-1;
let [first, ] = this.scroll.line(Math.min(range.index, limit));
let last = first;
if (range.length > 0) {
[last, ] = this.scroll.line(Math.min(range.index + range.length, limit));
}
if (first == null || last == null) return;
let scrollBounds = scrollingContainer.getBoundingClientRect();
if (bounds.top < scrollBounds.top) {
scrollingContainer.scrollTop -= (scrollBounds.top - bounds.top);
} else if (bounds.bottom > scrollBounds.bottom) {
scrollingContainer.scrollTop += (bounds.bottom - scrollBounds.bottom);
}
}
setNativeRange(startNode, startOffset, endNode = startNode, endOffset = startOffset, force = false) {
debug.info('setNativeRange', startNode, startOffset, endNode, endOffset);
if (startNode != null && (this.root.parentNode == null || startNode.parentNode == null || endNode.parentNode == null)) {
return;
}
let selection = document.getSelection();
if (selection == null) return;
if (startNode != null) {
if (!this.hasFocus()) this.root.focus();
let native = (this.getNativeRange() || {}).native;
if (native == null || force ||
startNode !== native.startContainer ||
startOffset !== native.startOffset ||
endNode !== native.endContainer ||
endOffset !== native.endOffset) {
if (startNode.tagName == "BR") {
startOffset = [].indexOf.call(startNode.parentNode.childNodes, startNode);
startNode = startNode.parentNode;
}
if (endNode.tagName == "BR") {
endOffset = [].indexOf.call(endNode.parentNode.childNodes, endNode);
endNode = endNode.parentNode;
}
let range = document.createRange();
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
selection.removeAllRanges();
selection.addRange(range);
}
} else {
selection.removeAllRanges();
this.root.blur();
document.body.focus(); // root.blur() not enough on IE11+Travis+SauceLabs (but not local VMs)
}
}
setRange(range, force = false, source = Emitter.sources.API) {
if (typeof force === 'string') {
source = force;
force = false;
}
debug.info('setRange', range);
if (range != null) {
let args = this.rangeToNative(range);
this.setNativeRange(...args, force);
} else {
this.setNativeRange(null);
}
this.update(source);
}
update(source = Emitter.sources.USER) {
let oldRange = this.lastRange;
let [lastRange, nativeRange] = this.getRange();
this.lastRange = lastRange;
if (this.lastRange != null) {
this.savedRange = this.lastRange;
}
if (!equal(oldRange, this.lastRange)) {
if (!this.composing && nativeRange != null && nativeRange.native.collapsed && nativeRange.start.node !== this.cursor.textNode) {
this.cursor.restore();
}
let args = [Emitter.events.SELECTION_CHANGE, clone(this.lastRange), clone(oldRange), source];
this.emitter.emit(Emitter.events.EDITOR_CHANGE, ...args);
if (source !== Emitter.sources.SILENT) {
this.emitter.emit(...args);
}
}
}
}
function contains(parent, descendant) {
try {
// Firefox inserts inaccessible nodes around video elements
descendant.parentNode;
} catch (e) {
return false;
}
// IE11 has bug with Text nodes
// https://connect.microsoft.com/IE/feedback/details/780874/node-contains-is-incorrect
if (descendant instanceof Text) {
descendant = descendant.parentNode;
}
return parent.contains(descendant);
}
export { Range, Selection as default };

30
assets/js/core/theme.js Normal file
View File

@@ -0,0 +1,30 @@
class Theme {
constructor(quill, options) {
this.quill = quill;
this.options = options;
this.modules = {};
}
init() {
Object.keys(this.options.modules).forEach((name) => {
if (this.modules[name] == null) {
this.addModule(name);
}
});
}
addModule(name) {
let moduleClass = this.quill.constructor.import(`modules/${name}`);
this.modules[name] = new moduleClass(this.quill, this.options.modules[name] || {});
return this.modules[name];
}
}
Theme.DEFAULTS = {
modules: {}
};
Theme.themes = {
'default': Theme
};
export default Theme;