Created
May 27, 2024 18:35
-
-
Save seanconnelly34/16b8066bf39710c8ea894cfc049db6e6 to your computer and use it in GitHub Desktop.
IframeScript.js
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
/**This script gets injected into the stencil template iframes on the edit page and | |
* is not processed as part of the app webpack build. This is why the file is self | |
* contained and does not use any imports. Due to the way create-react-app locks down | |
* the webpack settings, we were not able to modularize this file. | |
*/ | |
const domLoaded = () => { | |
// variables | |
const __parentWindow = window.parent; | |
const __parentOrigin = window.origin; | |
const editableElements = document.querySelectorAll('[contenteditable]:not(.custom)'); | |
const customElements = document.getElementsByClassName('custom'); | |
const customTextElements = document.getElementsByClassName('custom-text'); | |
const moveableElements = document.querySelectorAll('[data-customizable]'); | |
const guidelineElements = Array.from(moveableElements).filter( | |
(node) => !node.dataset.customizable.includes('text') | |
); | |
const imageElements = Array.from(moveableElements).filter( | |
(node) => node.nodeName === 'IMG' && node.id !== 'propertyQrCode' | |
); | |
let newImgSrc = ''; | |
let variableName = ''; | |
let imgSources = imageElements.map((img) => ({ id: img.id, src: img.src })); | |
let iframeWidth = document.body.offsetWidth; | |
let iframeHeight = document.body.offsetHeight; | |
// store element location and mousedown timestamp to determine if user moved on click event | |
let elementLocation = {}; | |
let mousedownTime = 0; | |
let lastClicked = null; | |
// store the initial zoom for moveable | |
let initialZoom = 1; | |
// make the custom text elements editable | |
Array.from(customTextElements).forEach((node) => (node.contentEditable = true)); | |
// functions | |
const getAllEditableFieldsAsMergeVariables = function () { | |
let mergeVariables = []; | |
Array.prototype.forEach.call(editableElements, function (el) { | |
const name = el.id; | |
const page = `${window.name}`; | |
const value = el.nodeName === 'IMG' ? el.src : el.innerHTML; | |
mergeVariables.push({ name, page, value }); | |
}); | |
return mergeVariables; | |
}; | |
const setAllEditableFieldsAsMergeVariables = function (mergeVariables) { | |
mergeVariables.forEach(function (item) { | |
const name = item.name; | |
const value = item.value; | |
const el = document.getElementById(name); | |
if (!el) return; | |
el.innerHTML = value; | |
}); | |
}; | |
const isMoveable = (element) => element.dataset?.customizable?.includes('move'); | |
const isResizable = (element) => element.dataset?.customizable?.includes('resize'); | |
const sendPostMessage = (data) => { | |
__parentWindow.postMessage(data, __parentOrigin); | |
}; | |
const handleImgDrop = ({ target }, name) => { | |
// for some reason we don't know what the new image should be, don't change | |
if (!newImgSrc) return; | |
target.src = newImgSrc; | |
if (target.classList.contains('custom')) { | |
sendPostMessage({ type: 'updateCustomImage', id: target.id, src: newImgSrc, variableName }); | |
} else { | |
sendPostMessage({ | |
type: 'updateField', | |
name, | |
value: newImgSrc, | |
resetImages: 'true', | |
page: window.name, | |
}); | |
} | |
let imgIndex = imgSources.findIndex((img) => img.id === target.id); | |
if (imgIndex !== -1) imgSources[imgIndex].src = newImgSrc; | |
target.style.opacity = ''; | |
target.style.border = ''; | |
resetEditing(); | |
}; | |
const handleDragEnter = ({ target }) => { | |
target.style.opacity = 1; | |
if (newImgSrc) target.src = newImgSrc; | |
target.style.border = 'solid 2px green'; | |
}; | |
const handleDragLeave = ({ target }) => { | |
target.style.opacity = ''; | |
target.style.border = ''; | |
target.src = imgSources.find((img) => img.id === target.id).src; | |
}; | |
const setImagesSelectable = (isSelectable) => { | |
imageElements.forEach((img) => { | |
if (isSelectable) { | |
img.classList.add('can-select'); | |
} else { | |
img.classList.remove('can-select'); | |
} | |
}); | |
}; | |
const hideCallToAction = (hide) => { | |
let ctaElem = document.getElementById('_cta') || document.getElementById('cta'); | |
if (ctaElem === null) return; | |
ctaElem.style.display = hide ? 'none' : 'initial'; | |
}; | |
const insertCallToAction = (cta) => { | |
let ctaElem = document.getElementById('_cta') || document.getElementById('cta'); | |
if (ctaElem === null) return; | |
ctaElem.innerHTML = cta; | |
}; | |
const updateAllFields = (newData) => { | |
newData.forEach((field) => { | |
let node = document.getElementById(field.name); | |
if (node?.nodeName === 'IMG') node.src = field.value; | |
else if (node) node.innerHTML = field.value; | |
}); | |
}; | |
const removeEditing = () => { | |
window.getSelection().removeAllRanges(); | |
document.activeElement.blur(); | |
document.querySelectorAll('[data-customizable]').forEach((el) => { | |
el.classList.remove('editing'); | |
el.classList.remove('can-select'); | |
}); | |
}; | |
/** Set the selected element and edit mode in live editor | |
* @param {Element} target - The element to be set | |
* @param {string} [mode=move] - The editor mode "move" or "text" | |
*/ | |
const setSelectedElement = (target, mode = 'move') => { | |
if (mode === 'move') removeEditing(); | |
editingElement = target; | |
if (!editingElement?.dataset?.customizable) { | |
return; | |
} | |
const compStyles = window.getComputedStyle(editingElement); | |
const canResize = !!isResizable(target); | |
// handle image select | |
const isImage = target.nodeName === 'IMG' && isMoveable(target); | |
const isShape = editingElement?.dataset?.customizable?.includes('shape'); | |
if (isImage || isShape) { | |
setMoveableTarget(target); | |
moveable.resizable = canResize; | |
const currentStyles = { | |
opacity: compStyles.opacity, | |
objectFit: compStyles.objectFit, | |
objectPosition: compStyles.objectPosition, | |
fill: compStyles.fill, | |
stroke: compStyles.stroke, | |
strokeWidth: parseInt(compStyles.strokeWidth), | |
width: compStyles.width, | |
height: compStyles.height, | |
zIndex: Number.isNaN(parseInt(compStyles.zIndex)) ? 0 : parseInt(compStyles.zIndex), | |
}; | |
sendPostMessage({ | |
type: 'setEditing', | |
id: editingElement.id, | |
currentStyles, | |
editType: isImage ? 'image' : 'shape', | |
editMode: 'move', | |
canResize, | |
}); | |
return; | |
} | |
// handle text select | |
if (editingElement?.dataset?.customizable?.includes('text')) { | |
if (mode === 'text' && editingElement.contentEditable) { | |
// set element to text mode | |
if (document.activeElement !== editingElement) { | |
editingElement.focus(); | |
// set caret to end rather than front | |
document.execCommand('selectAll', false, null); | |
document.getSelection().collapseToEnd(); | |
} | |
setMoveableTarget(null); | |
editingElement.classList.add('editing'); | |
} else { | |
// set element to moveable mode | |
editingElement.blur(); | |
setMoveableTarget(editingElement); | |
moveable.resizable = canResize; | |
} | |
const [fontSize, lineHeight, letterSpacing, zIndex] = [ | |
{ prop: 'font-size', default: 16 }, | |
{ prop: 'line-height', default: undefined }, | |
{ prop: 'letter-spacing', default: 0 }, | |
{ prop: 'z-index', default: 0 }, | |
].map((item) => { | |
const val = parseInt(compStyles[item.prop].replace('px', '')); | |
return Number.isNaN(val) ? item.default : val; | |
}); | |
const currentStyles = { | |
fontFamily: compStyles.fontFamily.replace(/"/g, ''), | |
fontSize, | |
lineHeight: lineHeight ?? fontSize * 1.2, | |
letterSpacing, | |
zIndex, | |
textAlign: compStyles.textAlign, | |
fontWeight: compStyles.fontWeight, | |
fontStyle: compStyles.fontStyle, | |
textDecoration: compStyles.textDecoration, | |
textTransform: compStyles.textTransform, | |
color: compStyles.color, | |
opacity: compStyles.opacity, | |
}; | |
sendPostMessage({ | |
type: 'setEditing', | |
id: editingElement.id, | |
currentStyles, | |
editType: 'text', | |
editMode: mode, | |
}); | |
} else { | |
sendPostMessage({ type: 'setEditing', id: '' }); | |
setMoveableTarget(null); | |
} | |
}; | |
function htmlToElement(html) { | |
var template = document.createElement('template'); | |
html = html.trim(); // Never return a text node of whitespace as the result | |
template.innerHTML = html; | |
return template.content.firstChild; | |
} | |
const setFieldValue = (data) => { | |
const { id, value } = data; | |
const el = document.getElementById(id); | |
if (el?.nodeName === 'IMG') el.src = value; | |
else if (el.contentEditable) el.innerHTML = value; | |
}; | |
/** When adding a custom element to the iframe this function will return a promise | |
* once the element has a position of "absolute". This lets us know that the custom | |
* CSS has been applied before taking further actions. | |
* @param {HTMLElement} node - The node to check the CSS of | |
*/ | |
const waitForNodeCSS = (node) => { | |
return new Promise((resolve) => { | |
const observer = new MutationObserver(() => { | |
const nodePosition = window.getComputedStyle(node).getPropertyValue('position'); | |
if (nodePosition === 'absolute') { | |
resolve(); | |
observer.disconnect(); | |
} | |
}); | |
observer.observe(document.getElementById('custom-styles'), { | |
childList: true, | |
subtree: true, | |
characterData: true, | |
}); | |
document.body.appendChild(node); | |
}); | |
}; | |
/** Handle postMessage events from the parent window | |
* @param {Event} e - The postMessage event | |
*/ | |
async function receiver(e) { | |
let root = document.documentElement; | |
const type = e.data?.type; | |
if (Array.isArray(e.data)) { | |
setAllEditableFieldsAsMergeVariables(e.data); | |
} else if (e.data === 'getAllEditableFieldsAsMergeVariables') { | |
sendPostMessage({ type: 'setFields', fields: getAllEditableFieldsAsMergeVariables() }); | |
} else if (type === 'updateBrandColor') { | |
root.style.setProperty('--brand-color', e.data.value); | |
} else if (type === 'imageSelected') { | |
newImgSrc = e.data?.imgSrc; | |
variableName = e.data?.variableName; | |
setImagesSelectable(!!e.data?.imgSrc); | |
} else if (type === 'hideCTA') { | |
const { hideCTA } = e.data; | |
hideCallToAction(hideCTA); | |
} else if (type === 'cta') { | |
const { CTA } = e.data; | |
insertCallToAction(CTA); | |
} else if (type === 'updateAllFields') { | |
updateAllFields(e.data?.replaceFieldData); | |
} else if (type === 'setFieldValue') { | |
setFieldValue(e.data); | |
} else if (type === 'customStyles') { | |
let customStyles = document.getElementById('custom-styles'); | |
if (customStyles) customStyles.innerHTML = e.data?.fullCssString; | |
} else if (type === 'removeTransform') { | |
const node = document.getElementById(e.data.id); | |
node.style.transform = ''; | |
if (moveable.target) { | |
setTimeout(() => moveable.updateTarget(), 0); | |
} else { | |
setSelectedElement(node); | |
} | |
} else if (type === 'resetSelected') { | |
removeEditing(); | |
if (moveable?.target) moveable.target = null; | |
} else if (type === 'addElement') { | |
// want iFrame to focus so that it is possible to move the newly added element ... | |
// ... without clicking on it first inside the document | |
window.focus(); | |
const { content, id, src } = e.data; | |
const newNode = htmlToElement(content); | |
addEditListeners(newNode, true); | |
addClickListeners(newNode); | |
await waitForNodeCSS(newNode); | |
if (src) { | |
// adding an image node | |
const onLoad = () => { | |
setSelectedElement(newNode); | |
return newNode.removeEventListener('load', onLoad); | |
}; | |
imgSources.push({ id, src }); | |
imageElements.push(newNode); | |
newNode.addEventListener('load', onLoad); | |
} else { | |
// adding a text or shape node | |
moveable.elementGuidelines.push(newNode); | |
setSelectedElement(newNode); | |
} | |
} else if (type === 'resizeElement') { | |
const { id, dim, value } = e.data; | |
document.getElementById(id).style[dim] = value; | |
moveable.updateRect(); | |
} else if (type === 'deleteElement') { | |
const { id } = e.data; | |
document.getElementById(id).remove(); | |
moveable.target = null; | |
} else if (type === 'hideElement') { | |
const { id } = e.data; | |
document.getElementById(id).style.visibility = 'hidden'; | |
moveable.target = null; | |
} else if (type === 'updateZoom') { | |
const zoom = 1 / e.data.zoomValue || 1; | |
initialZoom = zoom; | |
if (moveable) moveable.zoom = zoom; | |
} else { | |
// Type not recognized. Log the message. | |
console.log(JSON.stringify(e.data)); | |
} | |
} | |
function preventDefault(e) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
} | |
function loadError(oError) { | |
throw new URIError('The script ' + oError.target.src + " didn't load correctly."); | |
} | |
function prefixScript(url, onloadFunction) { | |
var newScript = document.createElement('script'); | |
newScript.onerror = loadError; | |
if (onloadFunction) { | |
newScript.onload = onloadFunction; | |
} | |
document.currentScript.parentNode.insertBefore(newScript, document.currentScript); | |
newScript.src = url; | |
} | |
// the element currently being edited | |
let editingElement = null; | |
const onScreen = (node) => { | |
if (!node) return false; | |
const { top, bottom, right, left } = node.getBoundingClientRect(); | |
const { clientWidth, clientHeight } = document.body; | |
if (top < 0 || left < 0 || right > clientWidth || bottom > clientHeight) return false; | |
return true; | |
}; | |
const setRotateHandle = (target) => { | |
const rotateHandle = document.querySelector('.moveable-rotation-control'); | |
if (!target || !rotateHandle) return; | |
moveable.rotationPosition = 'top'; | |
if (onScreen(rotateHandle)) return; | |
moveable.rotationPosition = 'bottom'; | |
if (onScreen(rotateHandle)) return; | |
moveable.rotationPosition = 'left'; | |
if (onScreen(rotateHandle)) return; | |
moveable.rotationPosition = 'right'; | |
if (onScreen(rotateHandle)) return; | |
moveable.rotationPosition = 'top-left'; | |
if (onScreen(rotateHandle)) return; | |
moveable.rotationPosition = 'top-right'; | |
if (onScreen(rotateHandle)) return; | |
moveable.rotationPosition = 'bottom-left'; | |
if (onScreen(rotateHandle)) return; | |
moveable.rotationPosition = 'bottom-right'; | |
}; | |
// add the moveable instance | |
let moveable = null; | |
const setMoveableTarget = (node) => { | |
document.querySelectorAll('.moving').forEach((node) => node.classList.remove('moving')); | |
if (moveable) moveable.target = node; | |
setRotateHandle(node); | |
if (node) node.classList.add('moving'); | |
}; | |
// Add moveable library | |
prefixScript('//daybrush.com/moveable/release/0.24.5/dist/moveable.min.js', () => { | |
// eslint-disable-next-line no-undef | |
moveable = new Moveable(document.body, { | |
draggable: true, | |
rotatable: true, | |
origin: false, | |
throttleRotate: 1, | |
snappable: true, | |
snapVertical: true, | |
snapHorizontal: true, | |
snapElement: true, | |
snapCenter: true, | |
elementGuidelines: guidelineElements, | |
verticalGuidelines: [ | |
0, | |
iframeWidth * 0.2, | |
iframeWidth * 0.4, | |
iframeWidth * 0.5, | |
iframeWidth * 0.6, | |
iframeWidth * 0.8, | |
iframeWidth, | |
], | |
horizontalGuidelines: [ | |
0, | |
iframeHeight * 0.2, | |
iframeHeight * 0.4, | |
iframeHeight * 0.5, | |
iframeHeight * 0.6, | |
iframeHeight * 0.8, | |
iframeHeight, | |
], | |
snapThreshold: 5, | |
zoom: initialZoom, | |
}); | |
moveable | |
.on('drag', ({ target, transform }) => { | |
target.style.transform = transform; | |
setRotateHandle(target); | |
}) | |
.on('dragEnd', ({ target, lastEvent }) => { | |
if (lastEvent) { | |
sendPostMessage({ | |
type: 'updateCSS', | |
id: target.id, | |
newData: { transform: lastEvent.transform }, | |
}); | |
} | |
}) | |
.on('rotate', ({ target, transform }) => { | |
target.style.transform = transform; | |
}) | |
.on('rotateEnd', ({ target, lastEvent }) => { | |
if (lastEvent) { | |
sendPostMessage({ | |
type: 'updateCSS', | |
id: target.id, | |
newData: { transform: lastEvent.transform }, | |
}); | |
} | |
}) | |
.on('resize', ({ target, inputEvent, width, height, drag: { transform } }) => { | |
moveable.keepRatio = inputEvent.shiftKey; | |
target.style.width = Math.max(width, 6) + 'px'; | |
target.style.height = Math.max(height, 6) + 'px'; | |
target.style.transform = transform; | |
}) | |
.on('resizeEnd', ({ target, lastEvent }) => { | |
if (lastEvent) { | |
const { | |
width, | |
height, | |
drag: { transform }, | |
} = lastEvent; | |
sendPostMessage({ | |
type: 'updateCSS', | |
id: target.id, | |
newData: { | |
transform, | |
width: width + 'px', | |
height: height + 'px', | |
}, | |
}); | |
// update input dimensions | |
setSelectedElement(target, 'move'); | |
} | |
}); | |
}); | |
// If the element was moved or the click lasted longer than 500ms assume the user intended a drag event | |
// do not set to text edit mode in these cases | |
function wasElementDragged(start, end, mouseupTime) { | |
return ( | |
Math.abs(start.x - end.x) > 5 || | |
Math.abs(start.y - end.y) > 5 || | |
mouseupTime - mousedownTime > 500 | |
); | |
} | |
function findAndSet(e) { | |
const { type, currentTarget } = e; | |
// only run focus event if tab action (not from user click) | |
if (type === 'focus' && currentTarget === lastClicked) { | |
lastClicked = null; | |
return; | |
} | |
const isDrag = wasElementDragged( | |
elementLocation, | |
currentTarget.getBoundingClientRect(), | |
Date.now() | |
); | |
const isEditing = currentTarget.classList.contains('editing'); | |
// determine movable vs text mode on click | |
const editMode = (isDrag || currentTarget !== moveable?.target) && !isEditing ? 'move' : 'text'; | |
setSelectedElement(currentTarget, editMode); | |
sendPostMessage({ elementClicked: true }); | |
} | |
window.addEventListener('message', receiver, false); | |
// prevent drop events on elements that are not images | |
document.querySelectorAll('body :not(img)').forEach((node) => { | |
node.addEventListener('drop', (e) => { | |
e.preventDefault(); | |
resetEditing(); | |
}); | |
}); | |
const addClickListeners = (el) => { | |
el.addEventListener('mousedown', (e) => { | |
elementLocation = e.currentTarget.getBoundingClientRect(); | |
mousedownTime = Date.now(); | |
lastClicked = e.currentTarget; | |
}); | |
el.addEventListener('click', findAndSet); | |
el.addEventListener('focus', findAndSet); | |
el.addEventListener('dblclick', findAndSet); | |
}; | |
moveableElements.forEach((el) => { | |
addClickListeners(el); | |
}); | |
const resetEditing = () => { | |
removeEditing(); | |
if (moveable?.target) moveable.target = null; | |
sendPostMessage({ type: 'setEditing', id: '' }); | |
}; | |
const handleKeydown = (e) => { | |
const ARROW_KEYS = { | |
left: 'ArrowLeft', | |
down: 'ArrowDown', | |
right: 'ArrowRight', | |
up: 'ArrowUp', | |
}; | |
const MODIFIER_SCALING = { | |
alt: 10, | |
shift: 5, | |
default: 1, | |
}; | |
const { key, altKey, shiftKey, ctrlKey, metaKey, view } = e; | |
if (moveable.target && Object.values(ARROW_KEYS).includes(key)) { | |
e.preventDefault(); | |
const scale = altKey | |
? MODIFIER_SCALING.alt | |
: shiftKey | |
? MODIFIER_SCALING.shift | |
: MODIFIER_SCALING.default; | |
const direction = [ARROW_KEYS.left, ARROW_KEYS.right].includes(key) | |
? 'horizontal' | |
: 'vertical'; | |
const delta = ([ARROW_KEYS.left, ARROW_KEYS.down].includes(key) ? -1 : 1) * scale; | |
/** | |
* Define an edge property only if the ctrlKey (metaKey → Command on Mac) is used. | |
* Depending on the direction, the edge has either an 'x' or 'y' property ... | |
* ... indicating the absolute final position. | |
* | |
* @note all iFrames have the same widths and height → can use first | |
*/ | |
const edge = | |
ctrlKey || metaKey | |
? direction === 'horizontal' | |
? { x: key === ARROW_KEYS.left ? 0 : view.innerWidth } | |
: { y: key === ARROW_KEYS.up ? 0 : view.innerHeight } | |
: undefined; | |
/** | |
* without ctrlKey, 'edge' is undefined and movement is relative to current position | |
* with ctrlKey, movement is absolute (towards the edges of the document) | |
*/ | |
if (!edge) { | |
// top left is (0,0) so need to adjust delta for vertical to be negative | |
const deltaX = direction === 'horizontal' ? delta : 0; | |
const deltaY = direction === 'vertical' ? -1 * delta : 0; | |
moveable.request('draggable', { deltaX, deltaY }, true); | |
} else { | |
const { width, height } = moveable.getRect(); | |
if (edge.x !== undefined) { | |
// for right edge, need to adjust by the width of the target to make sure it is fully visible | |
const visibleX = edge.x > 0 ? edge.x - width : edge.x; | |
moveable.request('draggable', { x: visibleX, deltaY: 0 }, true); | |
} else if (edge.y !== undefined) { | |
// for bottom edge, need to adjust by the height of the target to make sure it is fully visible | |
const visibleY = edge.y > 0 ? edge.y - height : edge.y; | |
moveable.request('draggable', { deltaX: 0, y: visibleY }, true); | |
} | |
} | |
} else { | |
const { activeElement } = document; | |
const { lastChild } = activeElement ?? {}; | |
sendPostMessage({ | |
type: 'keydown', | |
key, | |
modifiers: { ctrlKey, metaKey, shiftKey, altKey }, | |
textEdit: activeElement.classList.contains('editing'), | |
imgSrc: lastChild.nodeName === 'IMG' ? lastChild.getAttribute('src') : undefined, | |
}); | |
} | |
}; | |
document.addEventListener('keydown', handleKeydown); | |
function strip(html) { | |
let doc = new DOMParser().parseFromString(html, 'text/html'); | |
return doc.body.textContent || ''; | |
} | |
function handlePaste(e) { | |
// Stop data actually being pasted into div | |
e.stopPropagation(); | |
e.preventDefault(); | |
// Get pasted data via clipboard API | |
const clipboardData = (e.clipboardData || window.clipboardData).getData('text'); | |
const textNode = document.createTextNode(strip(clipboardData)); | |
const selection = window.getSelection(); | |
if (!selection.rangeCount) return false; | |
selection.deleteFromDocument(); | |
const range = selection.getRangeAt(0); | |
range.insertNode(textNode); | |
// move cursor to end of selection | |
range.setStart(textNode, textNode.length); | |
range.setEnd(textNode, textNode.length); | |
selection.removeAllRanges(); | |
selection.addRange(range); | |
} | |
const addEditListeners = (el) => { | |
const name = el.id; | |
const value = el.innerHTML; | |
const isCustom = el.classList.contains('custom'); | |
let changed = false; | |
let newValue = null; | |
const listener = function () { | |
newValue = el.innerHTML; | |
if (value !== newValue) changed = true; | |
}; | |
const notifier = function () { | |
if (!changed) return; | |
if (!__parentWindow) return; | |
if (!__parentOrigin) return; | |
if (isCustom) sendPostMessage({ type: 'updateCustomEdit', id: name, text: newValue }); | |
else sendPostMessage({ type: 'updateField', name, value: newValue, page: window.name }); | |
}; | |
const updateAndNotify = () => { | |
listener(); | |
notifier(); | |
}; | |
if (el.nodeName === 'IMG' && el.id !== 'propertyQrCode') { | |
el.addEventListener('dragstart', (e) => e.preventDefault()); | |
el.addEventListener('drop', (e) => { | |
preventDefault(e); | |
handleImgDrop(e, name); | |
}); | |
el.addEventListener('dragenter', (e) => { | |
preventDefault(e); | |
handleDragEnter(e); | |
}); | |
el.addEventListener('dragover', (e) => e.preventDefault()); | |
el.addEventListener('dragleave', handleDragLeave); | |
} else { | |
if (el.contentEditable) el.style.minWidth = '1rem';a | |
el.addEventListener('input', updateAndNotify); | |
el.addEventListener('blur', notifier); | |
el.addEventListener('keyup', updateAndNotify); | |
el.addEventListener('paste', handlePaste); | |
el.addEventListener('change', updateAndNotify); | |
el.addEventListener('copy', listener); | |
el.addEventListener('cut', updateAndNotify); | |
el.addEventListener('delete', updateAndNotify); | |
el.addEventListener('mouseup', listener); | |
} | |
}; | |
Array.prototype.forEach.call(editableElements, function (el) { | |
addEditListeners(el); | |
}); | |
Array.prototype.forEach.call(customElements, function (el) { | |
addEditListeners(el); | |
}); | |
}; | |
window.onload = domLoaded(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment