Created
October 7, 2017 09:47
-
-
Save vasiltabakov/a3bf16f6e69da421cbff60389b9eb975 to your computer and use it in GitHub Desktop.
Quill editor editable image caption
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
These two are partially based on the quill-image-resize-module (the options part). | |
Of course they need to be registered like so: | |
Quill.register(TaskerFigure); | |
Quill.register('modules/figureOptions', FigureOptions); | |
Code has a lot of room for improvement whereas we only needed a working version in our project's initial development phase. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Quill from 'quill'; | |
import {TaskerFigure} from './TaskerFigure'; | |
import Parchment from 'parchment'; | |
export class FigureOptions { | |
quill: Quill; | |
figure; | |
overlay; | |
linkRange; | |
constructor(quill, options = {}) { | |
this.quill = quill; | |
this.quill.root.addEventListener('click', this.handleClick.bind(this), false); | |
(<HTMLDivElement>this.quill.root.parentNode).style.position = (<HTMLDivElement>this.quill.root.parentNode).style.position || 'relative'; | |
this.quill.on('selection-change', (range, oldRange, source) => { | |
this.linkRange = oldRange; | |
}); | |
} | |
handleClick(evt) { | |
if (!evt.target) { | |
return false; | |
} | |
if (this.setFigure(evt)) { | |
} else if (this.figure) { | |
// clicked on a non image | |
this.hide(); | |
} | |
} | |
setFigure(evt): boolean { | |
if (evt.target.classList.contains('tasker-figure')) { | |
this.setBaseFigure(evt.target); | |
return true; | |
} else { | |
let node = evt.target; | |
while (true) { | |
if (node.classList && node.classList.contains('tasker-figure')) { | |
break; | |
} else { | |
node = node.parentNode; | |
} | |
if (node === undefined || node === null) { | |
break; | |
} | |
} | |
if (node) { | |
this.setBaseFigure(node); | |
return true; | |
} | |
} | |
return false; | |
} | |
setBaseFigure(figure) { | |
if (this.figure === figure) { | |
return; | |
} | |
if (this.figure) { | |
this.hide(); | |
} | |
this.show(figure); | |
} | |
show(figure) { | |
this.figure = figure; | |
this.figure.classList.add('with-options'); | |
this.showOverlay(); | |
} | |
showOverlay() { | |
if (this.overlay) { | |
this.hideOverlay(); | |
} | |
this.quill.setSelection(null); | |
// prevent spurious text selection | |
this.setUserSelect('none'); | |
// listen for the image being deleted or moved | |
// document.addEventListener('keyup', this.checkImage, true); | |
// this.quill.root.addEventListener('input', this.checkImage, true); | |
// Create and add the overlay | |
this.overlay = document.createElement('div'); | |
const style = this.overlay.style; | |
style.position = 'absolute'; | |
style.background = '#fff'; | |
style.border = '2px solid #1E90FF'; | |
style.borderRadius = '5px'; | |
style.width = 'auto'; | |
style.display = 'table'; | |
style.padding = '10px'; | |
style.margin = '0 auto'; | |
style.left = '50%'; | |
style.transform = 'translateX(-50%)'; | |
style.borderBottomLeftRadius = '0'; | |
style.borderBottomRightRadius = '0'; | |
style.borderBottom = '2px solid #fff'; | |
this.overlay.classList.add('tasker-figure-options') | |
const inputField = document.createElement('input'); | |
inputField.value = TaskerFigure.value(this.figure).name; | |
inputField.placeholder = 'Enter image caption...'; | |
inputField.addEventListener('input', (event) => { | |
// console.log(child.value); | |
// this.quill.formatText(this.linkRange, 'link', value, Emitter.sources.USER); | |
// this.quill.format('taskerFigure', child.value); | |
if (this.linkRange) { | |
const delta = this.quill.formatText(this.linkRange.index, this.linkRange.length, 'tasker-caption', inputField.value, 'user'); | |
} | |
}); | |
this.overlay.appendChild(inputField); | |
const cancel = document.createElement('i'); | |
cancel.classList.add('material-icons'); | |
cancel.innerText = 'close'; | |
this.overlay.appendChild(cancel); | |
const remove = document.createElement('i'); | |
remove.classList.add('material-icons'); | |
remove.classList.add('delete'); | |
remove.innerText = 'delete_forever'; | |
this.overlay.appendChild(remove); | |
remove.addEventListener('click', (event) => { | |
const blot = Parchment.find(this.figure); | |
this.hide(); | |
blot.remove(); | |
}); | |
cancel.addEventListener('click', (event) => { | |
this.hide(); | |
}); | |
this.quill.root.parentNode.appendChild(this.overlay); | |
this.repositionElements(); | |
} | |
repositionElements() { | |
if (!this.overlay || !this.figure) { | |
return; | |
} | |
// position the overlay over the image | |
const parent: HTMLDivElement = <HTMLDivElement>this.quill.root.parentNode; | |
const imgRect = this.figure.getBoundingClientRect(); | |
const containerRect = parent.getBoundingClientRect(); | |
Object.assign(this.overlay.style, { | |
top: `${imgRect.top - containerRect.top + parent.scrollTop - 47.5}px`, | |
}); | |
} | |
hide() { | |
this.figure.classList.remove('with-options'); | |
this.hideOverlay(); | |
this.figure = undefined; | |
} | |
hideOverlay() { | |
if (!this.overlay) { | |
return; | |
} | |
// Remove the overlay | |
this.quill.root.parentNode.removeChild(this.overlay); | |
this.overlay = undefined; | |
// stop listening for image deletion or movement | |
// document.removeEventListener('keyup', this.checkImage); | |
// this.quill.root.removeEventListener('input', this.checkImage); | |
// reset user-select | |
this.setUserSelect(''); | |
} | |
setUserSelect(value) { | |
[ | |
'userSelect', | |
'mozUserSelect', | |
'webkitUserSelect', | |
'msUserSelect', | |
].forEach((prop) => { | |
// set on contenteditable element and <html> | |
this.quill.root.style[prop] = value; | |
document.documentElement.style[prop] = value; | |
}); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Quill from 'quill'; | |
const Embed = Quill.import('blots/embed'); | |
export class TaskerFigure extends Embed { | |
public static blotName = 'taskerFigure'; | |
public static tagName = 'div'; | |
static value(domNode): any { | |
const taskerImage = domNode.querySelector('.tasker-image'); | |
const taskerCaption = domNode.querySelector('.tasker-caption'); | |
if (taskerImage && taskerCaption) { | |
return { | |
original: { | |
url: taskerImage.dataset.originalUrl, | |
path: taskerImage.dataset.originalPath | |
}, | |
normal: { | |
url: taskerImage.dataset.normalUrl, | |
path: taskerImage.dataset.normalPath | |
}, | |
small: { | |
url: taskerImage.dataset.smallUrl, | |
path: taskerImage.dataset.smallPath | |
}, | |
name: taskerCaption.innerText | |
}; | |
} else { | |
return {}; | |
} | |
} | |
static create(value) { | |
const node = super.create(value); | |
node.setAttribute('contenteditable', false); | |
node.style.fontSize = '0'; | |
node.classList.add('tasker-figure'); | |
const image = document.createElement('img'); | |
image.setAttribute('src', value.normal.url); | |
image.classList.add('tasker-image'); | |
image.dataset.normalUrl = value.normal.url; | |
image.dataset.smallUrl = value.small.url; | |
image.dataset.originalUrl = value.original.url; | |
image.dataset.normalPath = value.normal.path; | |
image.dataset.smallPath = value.small.path; | |
image.dataset.originalPath = value.original.path; | |
node.appendChild(image); | |
const caption = document.createElement('a'); | |
caption.innerText = value.name; | |
caption.classList.add('tasker-caption'); | |
caption.setAttribute('href', value.original.url); | |
caption.setAttribute('target', '_blank'); | |
node.appendChild(caption); | |
return node; | |
} | |
format(format: any, value: any) { | |
if (format === 'tasker-caption') { | |
this.domNode.querySelector('.tasker-caption').innerText = value; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment