Comparing version 1.0.0-beta.8 to 1.0.0-beta.9
@@ -82,6 +82,8 @@ import extend from 'extend'; | ||
} | ||
if (lines.length > 0) { | ||
let next = this.split(index + text.length, true); | ||
next.insertAt(0, lines.join('\n')); | ||
} | ||
let block = this; | ||
lines.reduce(function(index, line) { | ||
block = block.split(index, true); | ||
block.insertAt(0, line); | ||
return line.length; | ||
}, index + text.length); | ||
} | ||
@@ -88,0 +90,0 @@ |
@@ -83,4 +83,16 @@ import Parchment from 'parchment'; | ||
lines(index, length) { | ||
return this.descendants(isLine, index, length); | ||
lines(index = 0, length = Number.MAX_VALUE) { | ||
let getLines = (blot, index, length) => { | ||
let lines = [], lengthLeft = length; | ||
blot.children.forEachAt(index, length, function(child, index, length) { | ||
if (isLine(child)) { | ||
lines.push(child); | ||
} else if (child instanceof Parchment.Container) { | ||
lines = lines.concat(getLines(child, index, lengthLeft)); | ||
} | ||
lengthLeft -= length; | ||
}); | ||
return lines; | ||
}; | ||
return getLines(this, index, length); | ||
} | ||
@@ -87,0 +99,0 @@ |
@@ -0,1 +1,27 @@ | ||
# 1.0.0-beta.9 | ||
Potentially the final beta before a release candidate, if no major issues are discovered. | ||
### Breaking Changes | ||
- No longer expose `ui/link-tooltip` through `import` as implementation is now Snow specific | ||
- Significant refactoring of `ui/tooltip` | ||
- Syntax module now autodetects language, instead of defaulting to Javascript | ||
### Features | ||
- Formula and video insertion UI added to Snow and Bubble themes | ||
### Bug Fixes | ||
- Fix toolbar active state after backspacing to previous line [#730](https://github.com/quilljs/quill/issues/730) | ||
- User selection is now preserved various API calls [#731](https://github.com/quilljs/quill/issues/731) | ||
- Fix long click on link-tooltip [#747](https://github.com/quilljs/quill/issues/747) | ||
- Fix ordered list and text-align right interaction [#784](https://github.com/quilljs/quill/issues/784) | ||
- Fix toggling code block off [#789](https://github.com/quilljs/quill/issues/789) | ||
- Scroll position is now automatically preserved between editor blur and focus | ||
Thank you [@benbro](https://github.com/benbro), [@KameSama](https://github.com/KameSama), and [@sachinrekhi](https://github.com/sachinrekhi) for contributions to this release! | ||
# 1.0.0-beta.8 | ||
@@ -2,0 +28,0 @@ |
@@ -66,3 +66,3 @@ import Delta from 'rich-text/lib/delta'; | ||
this.updating = false; | ||
this.update(delta, source); | ||
return this.update(delta, source); | ||
} | ||
@@ -72,3 +72,3 @@ | ||
this.scroll.deleteAt(index, length); | ||
this.update(new Delta().retain(index).delete(length), source); | ||
return this.update(new Delta().retain(index).delete(length), source); | ||
} | ||
@@ -95,3 +95,3 @@ | ||
this.scroll.optimize(); | ||
this.update(new Delta().retain(index).retain(length, clone(formats)), source); | ||
return this.update(new Delta().retain(index).retain(length, clone(formats)), source); | ||
} | ||
@@ -103,3 +103,3 @@ | ||
}); | ||
this.update(new Delta().retain(index).retain(length, clone(formats)), source); | ||
return this.update(new Delta().retain(index).retain(length, clone(formats)), source); | ||
} | ||
@@ -153,3 +153,3 @@ | ||
this.scroll.insertAt(index, embed, value); | ||
this.update(new Delta().retain(index).insert({ [embed]: value }), source); | ||
return this.update(new Delta().retain(index).insert({ [embed]: value }), source); | ||
} | ||
@@ -163,3 +163,3 @@ | ||
}); | ||
this.update(new Delta().retain(index).insert(text, clone(formats)), source) | ||
return this.update(new Delta().retain(index).insert(text, clone(formats)), source) | ||
} | ||
@@ -185,3 +185,3 @@ | ||
let delta = new Delta().retain(index).concat(diff); | ||
this.applyDelta(delta, source); | ||
return this.applyDelta(delta, source); | ||
} | ||
@@ -203,2 +203,3 @@ | ||
} | ||
return change; | ||
} | ||
@@ -205,0 +206,0 @@ } |
@@ -110,3 +110,7 @@ import './polyfill'; | ||
[index, length, , source] = overload(index, length, source); | ||
this.editor.deleteText(index, length, source); | ||
let range = this.getSelection(); | ||
let change = this.editor.deleteText(index, length, source); | ||
range = shiftRange(range, index, -1*length, source); | ||
this.setSelection(range, Emitter.sources.SILENT); | ||
return change; | ||
} | ||
@@ -129,11 +133,14 @@ | ||
let range = this.getSelection(); | ||
if (range == null) return; | ||
let change = new Delta(); | ||
if (range == null) return change; | ||
if (Parchment.query(name, Parchment.Scope.BLOCK)) { | ||
this.formatLine(range, name, value, source); | ||
change = this.formatLine(range, name, value, source); | ||
} else if (range.length === 0) { | ||
return this.selection.format(name, value); | ||
this.selection.format(name, value); | ||
return change; | ||
} else { | ||
this.formatText(range, name, value, source); | ||
change = this.formatText(range, name, value, source); | ||
} | ||
this.setSelection(range, Emitter.sources.SILENT); | ||
return change; | ||
} | ||
@@ -144,3 +151,6 @@ | ||
[index, length, formats, source] = overload(index, length, name, value, source); | ||
this.editor.formatLine(index, length, formats, source); | ||
let range = this.getSelection(); | ||
let change = this.editor.formatLine(index, length, formats, source); | ||
this.setSelection(range, Emitter.sources.SILENT); | ||
return change; | ||
} | ||
@@ -151,3 +161,6 @@ | ||
[index, length, formats, source] = overload(index, length, name, value, source); | ||
this.editor.formatText(index, length, formats, source); | ||
let range = this.getSelection(); | ||
let change = this.editor.formatText(index, length, formats, source); | ||
this.setSelection(range, Emitter.sources.SILENT); | ||
return change; | ||
} | ||
@@ -200,9 +213,16 @@ | ||
insertEmbed(index, embed, value, source) { | ||
this.editor.insertEmbed(index, embed, value, source); | ||
let range = this.getSelection(); | ||
let change = this.editor.insertEmbed(index, embed, value, source); | ||
range = shiftRange(range, change, source); | ||
this.setSelection(range, Emitter.sources.SILENT); | ||
return change; | ||
} | ||
insertText(index, text, name, value, source) { | ||
let formats; | ||
let formats, range = this.getSelection(); | ||
[index, , formats, source] = overload(index, 0, name, value, source); | ||
this.editor.insertText(index, text, formats, source); | ||
let change = this.editor.insertText(index, text, formats, source); | ||
range = shiftRange(range, index, text.length, source); | ||
this.setSelection(range, Emitter.sources.SILENT); | ||
return change; | ||
} | ||
@@ -224,6 +244,6 @@ | ||
if (typeof index === 'string') { | ||
this.setContents(this.clipboard.convert(index), html); | ||
return this.setContents(this.clipboard.convert(index), html); | ||
} else { | ||
let paste = this.clipboard.convert(html); | ||
this.updateContents(new Delta().retain(index).concat(paste), source); | ||
return this.updateContents(new Delta().retain(index).concat(paste), source); | ||
} | ||
@@ -233,4 +253,8 @@ } | ||
removeFormat(index, length, source) { | ||
let range = this.getSelection(); | ||
[index, length, , source] = overload(index, length, source); | ||
this.editor.removeFormat(index, length, source); | ||
let change = this.editor.removeFormat(index, length, source); | ||
range = shiftRange(range, change, source); | ||
this.setSelection(range, Emitter.sources.SILENT); | ||
return change; | ||
} | ||
@@ -246,3 +270,3 @@ | ||
delta.delete(this.getLength()); | ||
this.editor.applyDelta(delta, source); | ||
return this.editor.applyDelta(delta, source); | ||
} | ||
@@ -262,15 +286,20 @@ | ||
let delta = new Delta().insert(text); | ||
this.setContents(delta, source); | ||
return this.setContents(delta, source); | ||
} | ||
update(source = Emitter.sources.USER) { | ||
this.scroll.update(source); // Will update selection before selection.update() does if text changes | ||
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) { | ||
let range = this.getSelection(); | ||
if (Array.isArray(delta)) { | ||
delta = new Delta(delta.slice()); | ||
} | ||
this.editor.applyDelta(delta, source); | ||
let change = this.editor.applyDelta(delta, source); | ||
range = shiftRange(range, change, source); | ||
this.setSelection(range, Emitter.sources.SILENT); | ||
return change; | ||
} | ||
@@ -326,3 +355,24 @@ } | ||
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 { | ||
if (source === Emitter.sources.USER) index -= 1; | ||
[start, end] = [range.index, range.index + range.length].map(function(pos) { | ||
if (index > pos) return pos; | ||
if (length >= 0) { | ||
return pos + length; | ||
} else { | ||
return Math.max(index, pos + length); | ||
} | ||
}); | ||
} | ||
return new Range(start, end - start); | ||
} | ||
export { overload, Quill as default }; |
@@ -34,2 +34,9 @@ import Parchment from 'parchment'; | ||
}); | ||
let scrollTop = this.root.scrollTop; | ||
this.root.addEventListener('blur', () => { | ||
scrollTop = this.root.scrollTop; | ||
}); | ||
this.root.addEventListener('focus', () => { | ||
this.root.scrollTop = scrollTop; | ||
}); | ||
this.emitter.on(Emitter.events.EDITOR_CHANGE, (type, delta) => { | ||
@@ -202,3 +209,3 @@ if (type === Emitter.events.TEXT_CHANGE && delta.length() > 0) { | ||
let [line, offset] = this.scroll.line(range.index + range.length); | ||
this.root.scrollTop = line.domNode.offsetTop + line.domNode.offsetHeight; | ||
this.root.scrollTop = line.domNode.offsetTop + line.domNode.offsetHeight - this.root.offsetHeight; | ||
} else if (bounds.top < 0) { | ||
@@ -205,0 +212,0 @@ let [line, offset] = this.scroll.line(range.index); |
@@ -44,3 +44,2 @@ import clone from 'clone'; | ||
this.quill.deleteText(range.index - 1, 1, Quill.sources.USER); | ||
this.quill.setSelection(range.index - 1, Quill.sources.SILENT); | ||
this.quill.selection.scrollIntoView(); | ||
@@ -51,3 +50,2 @@ }); | ||
this.quill.deleteText(range.index, 1, Quill.sources.USER); | ||
this.quill.setSelection(range.index, Quill.sources.SILENT); | ||
}); | ||
@@ -186,3 +184,2 @@ this.addBinding({ key: Keyboard.keys.BACKSPACE }, { collapsed: false }, handleDelete); | ||
this.quill.insertText(range.index, '\t', Quill.sources.USER); | ||
this.quill.setSelection(range.index + 1, Quill.sources.SILENT); | ||
} | ||
@@ -241,3 +238,2 @@ ], | ||
this.quill.insertText(range.index, '\n', lineFormats, Quill.sources.USER); | ||
this.quill.setSelection(range.index + 1, Quill.sources.SILENT); | ||
this.quill.selection.scrollIntoView(); | ||
@@ -254,27 +250,30 @@ Object.keys(context.format).forEach((name) => { | ||
let handler = function(range) { | ||
let tab = Parchment.query('code-block').TAB; | ||
let CodeBlock = Parchment.query('code-block'); | ||
let index = range.index, length = range.length; | ||
let lines = []; | ||
if (range.length === 0) { | ||
let [line, ] = this.quill.scroll.line(range.index); | ||
lines.push(line); | ||
} else { | ||
lines = this.quill.scroll.lines(range.index, range.length); | ||
} | ||
lines.forEach(function(line, i) { | ||
let [block, offset] = this.quill.scroll.descendant(CodeBlock, index); | ||
if (block == null) return; | ||
let scrollOffset = this.quill.scroll.offset(block); | ||
let start = block.newlineIndex(offset, true) + 1; | ||
let end = block.newlineIndex(scrollOffset + offset + length); | ||
let lines = block.domNode.textContent.slice(start, end).split('\n'); | ||
offset = 0; | ||
lines.forEach((line, i) => { | ||
if (indent) { | ||
line.insertAt(0, tab); | ||
block.insertAt(start + offset, CodeBlock.TAB); | ||
offset += CodeBlock.TAB.length; | ||
if (i === 0) { | ||
index += tab.length; | ||
index += CodeBlock.TAB.length; | ||
} else { | ||
length += tab.length; | ||
length += CodeBlock.TAB.length; | ||
} | ||
} else if (line.domNode.textContent.startsWith(tab)) { | ||
line.deleteAt(0, tab.length); | ||
} else if (line.startsWith(CodeBlock.TAB)) { | ||
block.deleteAt(start + offset, CodeBlock.TAB.length); | ||
offset -= CodeBlock.TAB.length; | ||
if (i === 0) { | ||
index -= tab.length; | ||
index -= CodeBlock.TAB.length; | ||
} else { | ||
length -= tab.length; | ||
length -= CodeBlock.TAB.length; | ||
} | ||
} | ||
offset += line.length + 1; | ||
}); | ||
@@ -281,0 +280,0 @@ this.quill.update(Quill.sources.USER); |
@@ -8,17 +8,2 @@ import Parchment from 'parchment'; | ||
class SyntaxCodeBlock extends CodeBlock { | ||
static create(value) { | ||
let domNode = super.create(value); | ||
if (typeof value === 'string') { | ||
domNode.dataset.language = value; | ||
} else { | ||
domNode.dataset.language = SyntaxCodeBlock.DEFAULT_LANGUAGE; | ||
} | ||
domNode.classList.add(domNode.dataset.language); | ||
return domNode; | ||
} | ||
static formats(domNode) { | ||
return domNode.dataset.language || SyntaxCodeBlock.DEFAULT_LANGUAGE; | ||
} | ||
replaceWith(block) { | ||
@@ -34,4 +19,3 @@ this.domNode.textContent = this.domNode.textContent; | ||
if (text.trim().length > 0 || this.cachedHTML == null) { | ||
this.domNode.textContent = text; | ||
highlight(this.domNode); | ||
this.domNode.innerHTML = highlight(text); | ||
this.attach(); | ||
@@ -43,3 +27,3 @@ } | ||
} | ||
SyntaxCodeBlock.DEFAULT_LANGUAGE = 'javascript'; | ||
SyntaxCodeBlock.className = 'ql-syntax'; | ||
@@ -56,3 +40,3 @@ | ||
if (typeof this.options.highlight !== 'function') { | ||
throw new Error('Syntax module requires highlight.js. Please include the library on the page.'); | ||
throw new Error('Syntax module requires highlight.js. Please include the library on the page before Quill.'); | ||
} | ||
@@ -84,3 +68,9 @@ Quill.register(CodeToken, true); | ||
Syntax.DEFAULTS = { | ||
highlight: (window.hljs != null ? window.hljs.highlightBlock: null) | ||
highlight: (function() { | ||
if (window.hljs == null) return null; | ||
return function(text) { | ||
let result = window.hljs.highlightAuto(text); | ||
return result.value; | ||
} | ||
})() | ||
}; | ||
@@ -87,0 +77,0 @@ |
@@ -38,3 +38,7 @@ import extend from 'extend'; | ||
}); | ||
this.quill.on(Quill.events.SELECTION_CHANGE, this.update, this); | ||
this.quill.on(Quill.events.EDITOR_CHANGE, (type, range) => { | ||
if (type === Quill.events.SELECTION_CHANGE) { | ||
this.update(range); | ||
} | ||
}); | ||
this.quill.on(Quill.events.SCROLL_OPTIMIZE, () => { | ||
@@ -95,4 +99,2 @@ let [range, ] = this.quill.selection.getRange(); // quill.getSelection triggers update | ||
, Quill.sources.USER); | ||
range = new Range(range.index + 1, 0); | ||
this.quill.setSelection(range, Quill.sources.SILENT); | ||
} else { | ||
@@ -131,3 +133,3 @@ this.quill.format(format, value, Quill.sources.USER); | ||
} else { | ||
input.classList.toggle('ql-active', formats[format] === true || false); | ||
input.classList.toggle('ql-active', formats[format] === true || (format === 'link' && formats[format] != null)); | ||
} | ||
@@ -198,2 +200,3 @@ }); | ||
Object.keys(formats).forEach((name) => { | ||
// Clean functionality in existing apps only clean inline formats | ||
if (Parchment.query(name, Parchment.Scope.INLINE) != null) { | ||
@@ -204,7 +207,3 @@ this.quill.format(name, false); | ||
} else { | ||
let startLength = this.quill.getLength(); | ||
this.quill.removeFormat(range, Quill.sources.USER); | ||
let endLength = this.quill.getLength(); | ||
// account for embed removals | ||
this.quill.setSelection(range.index, range.length - (startLength-endLength), Quill.sources.SILENT); | ||
} | ||
@@ -211,0 +210,0 @@ }, |
{ | ||
"name": "quill", | ||
"version": "1.0.0-beta.8", | ||
"version": "1.0.0-beta.9", | ||
"description": "Cross browser rich text editor", | ||
@@ -38,3 +38,3 @@ "author": "Jason Chen <jhchen7@gmail.com>", | ||
"extend": "~3.0.0", | ||
"parchment": "1.0.0-beta.8", | ||
"parchment": "1.0.0-beta.9", | ||
"rich-text": "~3.0.1" | ||
@@ -41,0 +41,0 @@ }, |
@@ -37,3 +37,2 @@ import Quill from './core'; | ||
import Tooltip from './ui/tooltip'; | ||
import LinkTooltip from './ui/link-tooltip'; | ||
@@ -95,4 +94,3 @@ import BubbleTheme from './themes/bubble'; | ||
'ui/color-picker': ColorPicker, | ||
'ui/tooltip': Tooltip, | ||
'ui/link-tooltip': LinkTooltip | ||
'ui/tooltip': Tooltip | ||
}, true); | ||
@@ -99,0 +97,0 @@ |
@@ -43,2 +43,19 @@ import extend from 'extend'; | ||
); | ||
let listener = (e) => { | ||
if (!document.body.contains(quill.root)) { | ||
return document.body.removeEventListener('click', listener); | ||
} | ||
if (this.tooltip != null && !this.tooltip.root.contains(e.target) && | ||
document.activeElement !== this.tooltip.textbox) { | ||
this.tooltip.hide(); | ||
} | ||
if (this.pickers != null) { | ||
this.pickers.forEach(function(picker) { | ||
if (!picker.container.contains(e.target)) { | ||
picker.close(); | ||
} | ||
}); | ||
} | ||
}; | ||
document.body.addEventListener('click', listener); | ||
} | ||
@@ -76,4 +93,3 @@ | ||
buildPickers(selects) { | ||
let pickers = selects.map((select) => { | ||
let picker; | ||
this.pickers = selects.map((select) => { | ||
if (select.classList.contains('ql-align')) { | ||
@@ -83,3 +99,3 @@ if (select.querySelector('option') == null) { | ||
} | ||
picker = new IconPicker(select, icons.align); | ||
return new IconPicker(select, icons.align); | ||
} else if (select.classList.contains('ql-background') || select.classList.contains('ql-color')) { | ||
@@ -90,3 +106,3 @@ let format = select.classList.contains('ql-background') ? 'background' : 'color'; | ||
} | ||
picker = new ColorPicker(select, icons[format]); | ||
return new ColorPicker(select, icons[format]); | ||
} else { | ||
@@ -102,8 +118,7 @@ if (select.querySelector('option') == null) { | ||
} | ||
picker = new Picker(select); | ||
return new Picker(select); | ||
} | ||
return picker; | ||
}); | ||
let update = function() { | ||
pickers.forEach(function(picker) { | ||
let update = () => { | ||
this.pickers.forEach(function(picker) { | ||
picker.update(); | ||
@@ -114,9 +129,2 @@ }); | ||
.on(Emitter.events.SCROLL_OPTIMIZE, update); | ||
document.body.addEventListener('click', (e) => { | ||
pickers.forEach(function(picker) { | ||
if (!(e.target.compareDocumentPosition(picker.container) & Node.DOCUMENT_POSITION_CONTAINS)) { | ||
picker.close(); | ||
} | ||
}); | ||
}); | ||
} | ||
@@ -130,3 +138,2 @@ } | ||
let fileInput = this.container.querySelector('input.ql-image[type=file]'); | ||
let quill = this.quill; | ||
if (fileInput == null) { | ||
@@ -137,8 +144,8 @@ fileInput = document.createElement('input'); | ||
fileInput.classList.add('ql-image'); | ||
fileInput.addEventListener('change', function() { | ||
if (this.files != null && this.files[0] != null) { | ||
fileInput.addEventListener('change', () => { | ||
if (fileInput.files != null && fileInput.files[0] != null) { | ||
let reader = new FileReader(); | ||
reader.onload = function(e) { | ||
let range = quill.getSelection(true); | ||
quill.updateContents(new Delta() | ||
reader.onload = (e) => { | ||
let range = this.quill.getSelection(true); | ||
this.quill.updateContents(new Delta() | ||
.retain(range.index) | ||
@@ -148,6 +155,5 @@ .delete(range.length) | ||
, Emitter.sources.USER); | ||
quill.setSelection(range.index + 1, Emitter.sources.SILENT); | ||
fileInput.value = ""; | ||
} | ||
reader.readAsDataURL(this.files[0]); | ||
reader.readAsDataURL(fileInput.files[0]); | ||
} | ||
@@ -154,0 +160,0 @@ }); |
@@ -15,25 +15,49 @@ import Emitter from '../core/emitter'; | ||
buildLinkEditor(toolbar) { | ||
let container = document.createElement('div'); | ||
container.classList.add('ql-link-editor'); | ||
let arrow = document.createElement('span'); | ||
arrow.classList.add('ql-tooltip-arrow'); | ||
let input = document.createElement('input'); | ||
input.setAttribute('type', 'text'); | ||
let close = document.createElement('a'); | ||
container.appendChild(input); | ||
container.appendChild(close); | ||
this.tooltip.root.appendChild(arrow); | ||
this.tooltip.root.appendChild(container); | ||
extendToolbar(toolbar) { | ||
this.tooltip = new BubbleTooltip(this.quill, this.options.bounds); | ||
this.tooltip.root.appendChild(toolbar.container); | ||
this.buildButtons([].slice.call(toolbar.container.querySelectorAll('button'))); | ||
this.buildPickers([].slice.call(toolbar.container.querySelectorAll('select'))); | ||
} | ||
} | ||
BubbleTheme.DEFAULTS = { | ||
modules: { | ||
toolbar: { | ||
container: [ | ||
['bold', 'italic', 'link'], | ||
[{ header: 1 }, { header: 2 }, 'blockquote'] | ||
], | ||
handlers: { | ||
formula: function(value) { | ||
this.quill.theme.tooltip.edit('formula'); | ||
}, | ||
link: function(value) { | ||
if (!value) { | ||
this.quill.format('link', false); | ||
} else { | ||
this.quill.theme.tooltip.edit(); | ||
} | ||
}, | ||
video: function(value) { | ||
this.quill.theme.tooltip.edit('video'); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
class BubbleTooltip extends Tooltip { | ||
constructor(quill, bounds) { | ||
super(quill, bounds); | ||
this.quill.on(Emitter.events.SELECTION_CHANGE, (range) => { | ||
if (range != null && range.length > 0) { | ||
this.tooltip.root.classList.remove('ql-editing'); | ||
this.tooltip.show(); | ||
this.show(); | ||
// Lock our width so we will expand beyond our offsetParent boundaries | ||
this.tooltip.root.style.left = '0px'; | ||
this.tooltip.root.style.width = ''; | ||
this.tooltip.root.style.width = this.tooltip.root.offsetWidth + 'px'; | ||
this.root.style.left = '0px'; | ||
this.root.style.width = ''; | ||
this.root.style.width = this.root.offsetWidth + 'px'; | ||
let lines = this.quill.scroll.lines(range.index, range.length); | ||
if (lines.length === 1) { | ||
this.tooltip.position(this.quill.getBounds(range)); | ||
this.position(this.quill.getBounds(range)); | ||
} else { | ||
@@ -44,73 +68,51 @@ let lastLine = lines[lines.length - 1]; | ||
let bounds = this.quill.getBounds(new Range(index, length)); | ||
this.tooltip.position(bounds); | ||
this.position(bounds); | ||
} | ||
} else if (document.activeElement !== input && !!this.quill.hasFocus()) { | ||
this.tooltip.hide(); | ||
} else if (document.activeElement !== this.textbox && this.quill.hasFocus()) { | ||
this.hide(); | ||
} | ||
}); | ||
} | ||
listen() { | ||
super.listen(); | ||
['mousedown', 'touchstart'].forEach((name) => { | ||
this.root.querySelector('.ql-close').addEventListener(name, (event) => { | ||
this.root.classList.remove('ql-editing'); | ||
event.preventDefault(); | ||
}); | ||
}); | ||
this.quill.on(Emitter.events.SCROLL_OPTIMIZE, () => { | ||
// Let selection be restored by toolbar handlers before repositioning | ||
setTimeout(() => { | ||
if (this.tooltip.root.classList.contains('ql-hidden')) return; | ||
if (this.root.classList.contains('ql-hidden')) return; | ||
let range = this.quill.getSelection(); | ||
if (range != null) { | ||
this.tooltip.position(this.quill.getBounds(range)); | ||
this.position(this.quill.getBounds(range)); | ||
} | ||
}, 1); | ||
}); | ||
toolbar.handlers['link'] = (value) => { | ||
if (!value) { | ||
this.quill.format('link', false); | ||
} else { | ||
this.tooltip.root.classList.add('ql-editing'); | ||
input.focus(); | ||
} | ||
}; | ||
['mousedown', 'touchstart'].forEach((name) => { | ||
close.addEventListener(name, (event) => { | ||
this.tooltip.root.classList.remove('ql-editing'); | ||
event.preventDefault(); | ||
}); | ||
}); | ||
input.addEventListener('keydown', (event) => { | ||
if (Keyboard.match(event, 'enter')) { | ||
let scrollTop = this.quill.root.scrollTop; | ||
this.quill.focus(); | ||
this.quill.root.scrollTop = scrollTop; | ||
this.quill.format('link', input.value); | ||
this.tooltip.hide(); | ||
input.value = ''; | ||
event.preventDefault(); | ||
} else if (Keyboard.match(event, 'escape')) { | ||
this.tooltip.classList.remove('ql-editing'); | ||
event.preventDefault(); | ||
} | ||
}); | ||
} | ||
extendToolbar(toolbar) { | ||
let container = this.quill.addContainer('ql-tooltip', this.quill.root); | ||
this.tooltip = new Tooltip(container, { | ||
bounds: this.options.bounds, | ||
scroll: this.quill.root | ||
}); | ||
this.buildLinkEditor(toolbar); | ||
container.appendChild(toolbar.container); | ||
this.buildButtons([].slice.call(toolbar.container.querySelectorAll('button'))); | ||
this.buildPickers([].slice.call(toolbar.container.querySelectorAll('select'))); | ||
this.tooltip.hide(); | ||
cancel() { | ||
this.show(); | ||
} | ||
} | ||
BubbleTheme.DEFAULTS = { | ||
modules: { | ||
toolbar: { | ||
container: [ | ||
['bold', 'italic', 'link'], | ||
[{ header: 1 }, { header: 2 }, 'blockquote'] | ||
] | ||
} | ||
position(reference) { | ||
let shift = super.position(reference); | ||
if (shift === 0) return shift; | ||
let arrow = this.root.querySelector('.ql-tooltip-arrow'); | ||
arrow.style.marginLeft = ''; | ||
arrow.style.marginLeft = (-1*shift - arrow.offsetWidth/2) + 'px'; | ||
} | ||
} | ||
BubbleTooltip.TEMPLATE = [ | ||
'<span class="ql-tooltip-arrow"></span>', | ||
'<div class="ql-tooltip-editor">', | ||
'<input type="text" data-formula="e=mc^2" data-link="quilljs.com" data-video="Embed URL">', | ||
'<a class="ql-close"></a>', | ||
'</div>' | ||
].join(''); | ||
export default BubbleTheme; |
import Emitter from '../core/emitter'; | ||
import BaseTheme from './base'; | ||
import LinkTooltip from '../ui/link-tooltip'; | ||
import LinkBlot from '../formats/link'; | ||
import Picker from '../ui/picker'; | ||
import { Range } from '../core/selection'; | ||
import Tooltip from '../ui/tooltip'; | ||
@@ -17,4 +19,4 @@ | ||
this.buildPickers([].slice.call(toolbar.container.querySelectorAll('select'))); | ||
this.tooltip = new SnowTooltip(this.quill, this.options.bounds); | ||
if (toolbar.container.querySelector('.ql-link')) { | ||
this.linkTooltip = new LinkTooltip(this.quill); | ||
this.quill.keyboard.addBinding({ key: 'K', shortKey: true }, function(range, context) { | ||
@@ -36,6 +38,15 @@ toolbar.handlers['link'].call(toolbar, !context.format.link); | ||
handlers: { | ||
formula: function(value) { | ||
this.quill.theme.tooltip.edit('formula'); | ||
}, | ||
link: function(value) { | ||
if (value) { | ||
let savedRange = this.quill.selection.savedRange; | ||
this.quill.theme.linkTooltip.open(savedRange); | ||
let range = this.quill.getSelection(); | ||
if (range == null || range.length == 0) return; | ||
let preview = this.quill.getText(range); | ||
if (/^\S+@\S+\.\S+$/.test(preview) && preview.indexOf('mailto:') !== 0) { | ||
preview = 'mailto:' + preview; | ||
} | ||
let tooltip = this.quill.theme.tooltip; | ||
tooltip.edit('link', preview); | ||
} else { | ||
@@ -45,2 +56,5 @@ this.quill.format('link', false); | ||
}, | ||
video: function(value) { | ||
this.quill.theme.tooltip.edit('video'); | ||
} | ||
} | ||
@@ -52,2 +66,51 @@ } | ||
class SnowTooltip extends Tooltip { | ||
constructor(quill, bounds) { | ||
super(quill, bounds); | ||
this.preview = this.root.querySelector('a.ql-preview'); | ||
} | ||
listen() { | ||
super.listen(); | ||
this.root.querySelector('a.ql-action').addEventListener('click', (event) => { | ||
if (this.root.classList.contains('ql-editing')) { | ||
this.save(); | ||
} else { | ||
this.edit('link', this.preview.textContent); | ||
} | ||
}); | ||
this.root.querySelector('a.ql-remove').addEventListener('click', (event) => { | ||
if (this.linkRange != null) { | ||
this.quill.focus(); | ||
this.quill.formatText(this.linkRange, 'link', false, Emitter.sources.USER); | ||
delete this.linkRange; | ||
} | ||
this.hide(); | ||
}); | ||
this.quill.on(Emitter.events.SELECTION_CHANGE, (range) => { | ||
if (range == null) return; | ||
if (range.length === 0) { | ||
let [link, offset] = this.quill.scroll.descendant(LinkBlot, range.index); | ||
if (link != null) { | ||
this.linkRange = new Range(range.index - offset, link.length()); | ||
let preview = LinkBlot.formats(link.domNode); | ||
this.preview.textContent = preview; | ||
this.preview.setAttribute('href', preview); | ||
this.show(); | ||
this.position(this.quill.getBounds(this.linkRange)); | ||
return; | ||
} | ||
} | ||
this.hide(); | ||
}); | ||
} | ||
} | ||
SnowTooltip.TEMPLATE = [ | ||
'<a class="ql-preview" target="_blank" href="about:blank"></a>', | ||
'<input type="text" data-formula="e=mc^2" data-link="quilljs.com" data-video="Embed URL">', | ||
'<a class="ql-action"></a>', | ||
'<a class="ql-remove"></a>' | ||
].join(''); | ||
export default SnowTheme; |
@@ -0,24 +1,67 @@ | ||
import Keyboard from '../modules/keyboard'; | ||
import Emitter from '../core/emitter'; | ||
class Tooltip { | ||
constructor(root, containers = {}) { | ||
this.containers = containers; | ||
this.root = root; | ||
this.root.classList.add('ql-tooltip'); | ||
if (this.containers.scroll instanceof HTMLElement) { | ||
let offset = parseInt(window.getComputedStyle(this.root).marginTop); | ||
this.containers.scroll.addEventListener('scroll', () => { | ||
this.root.style.marginTop = (-1*this.containers.scroll.scrollTop) + offset + 'px'; | ||
}); | ||
constructor(quill, boundsContainer) { | ||
this.quill = quill; | ||
this.boundsContainer = boundsContainer; | ||
this.root = quill.addContainer('ql-tooltip'); | ||
this.root.innerHTML = this.constructor.TEMPLATE; | ||
let offset = parseInt(window.getComputedStyle(this.root).marginTop); | ||
this.quill.root.addEventListener('scroll', () => { | ||
this.root.style.marginTop = (-1*this.quill.root.scrollTop) + offset + 'px'; | ||
this.checkBounds(); | ||
}); | ||
this.textbox = this.root.querySelector('input[type="text"]'); | ||
this.listen(); | ||
this.hide(); | ||
} | ||
checkBounds() { | ||
this.root.classList.toggle('ql-out-top', this.root.offsetTop <= 0); | ||
this.root.classList.remove('ql-out-bottom'); | ||
this.root.classList.toggle('ql-out-bottom', this.root.offsetTop + this.root.offsetHeight >= this.quill.root.offsetHeight); | ||
} | ||
listen() { | ||
this.textbox.addEventListener('keydown', (event) => { | ||
if (Keyboard.match(event, 'enter')) { | ||
this.save(); | ||
event.preventDefault(); | ||
} else if (Keyboard.match(event, 'escape')) { | ||
this.cancel(); | ||
event.preventDefault(); | ||
} | ||
}); | ||
} | ||
cancel() { | ||
this.hide(); | ||
} | ||
edit(mode = 'link', preview = null) { | ||
this.root.classList.remove('ql-hidden'); | ||
this.root.classList.add('ql-editing'); | ||
if (preview != null) { | ||
this.textbox.value = preview; | ||
} else if (mode !== this.root.dataset.mode) { | ||
this.textbox.value = ''; | ||
} | ||
this.textbox.select(); | ||
this.textbox.setAttribute('placeholder', this.textbox.dataset[mode] || ''); | ||
this.root.dataset.mode = mode; | ||
this.position(this.quill.getBounds(this.quill.selection.savedRange)); | ||
} | ||
hide() { | ||
this.root.classList.add('ql-hidden'); | ||
} | ||
position(reference) { | ||
let left = reference.left + reference.width/2 - this.root.offsetWidth/2; | ||
let top = reference.bottom; | ||
if (this.containers.scroll instanceof HTMLElement) { | ||
top += this.containers.scroll.scrollTop; | ||
} | ||
let top = reference.bottom + this.quill.root.scrollTop; | ||
this.root.style.left = left + 'px'; | ||
this.root.style.top = top + 'px'; | ||
if (!(this.containers.bounds instanceof HTMLElement)) return; | ||
let containerBounds = this.containers.bounds.getBoundingClientRect(); | ||
let containerBounds = this.boundsContainer.getBoundingClientRect(); | ||
let rootBounds = this.root.getBoundingClientRect(); | ||
@@ -34,17 +77,42 @@ let shift = 0; | ||
} | ||
let arrow = this.root.querySelector('.ql-tooltip-arrow'); | ||
if (arrow == null) return; | ||
arrow.style.marginLeft = ''; | ||
if (shift !== 0) { | ||
arrow.style.marginLeft = (-1*shift - arrow.offsetWidth/2) + 'px'; | ||
this.checkBounds(); | ||
return shift; | ||
} | ||
save() { | ||
switch(this.root.dataset.mode) { | ||
case 'link': | ||
let url = this.textbox.value; | ||
let scrollTop = this.quill.root.scrollTop; | ||
if (this.linkRange) { | ||
this.quill.formatText(this.linkRange, 'link', url, Emitter.sources.USER); | ||
delete this.linkRange; | ||
} else { | ||
this.quill.focus(); | ||
this.quill.format('link', url, Emitter.sources.USER); | ||
} | ||
this.quill.root.scrollTop = scrollTop; | ||
break; | ||
case 'formula': // fallthrough | ||
case 'video': | ||
let range = this.quill.getSelection(true); | ||
let index = range.index + range.length; | ||
if (range != null) { | ||
this.quill.insertEmbed(index, this.root.dataset.mode, this.textbox.value, Emitter.sources.USER); | ||
if (this.root.dataset.mode === 'formula') { | ||
this.quill.insertText(index + 1, ' ', Emitter.sources.USER); | ||
} | ||
this.quill.setSelection(index + 2, Emitter.sources.USER); | ||
} | ||
break; | ||
default: | ||
} | ||
this.textbox.value = ''; | ||
this.hide(); | ||
} | ||
show() { | ||
this.root.classList.remove('ql-editing'); | ||
this.root.classList.remove('ql-hidden'); | ||
} | ||
hide() { | ||
this.root.classList.add('ql-hidden'); | ||
} | ||
} | ||
@@ -51,0 +119,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
620527
13563
132
+ Addedparchment@1.0.0-beta.9(transitive)
- Removedparchment@1.0.0-beta.8(transitive)
Updatedparchment@1.0.0-beta.9