Skip to content
This repository has been archived by the owner on Nov 28, 2022. It is now read-only.

Commit

Permalink
🐛 Koenig - Fix embedding of multiple FB Videos
Browse files Browse the repository at this point in the history
refs TryGhost/Ghost#9623
- wrap all embeds in an `<iframe>` so that their scripts are isolated (fixes FB Video)
- add `MutationObserver` implementation to adjust iframe height as embed's content is loaded
- add `noframe.js` to resize embedded iframes such as YouTube videos
  • Loading branch information
kevinansfield committed Jun 13, 2018
1 parent f9b08d8 commit 30a362c
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 36 deletions.
5 changes: 5 additions & 0 deletions ember-cli-build.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ module.exports = function (defaults) {
app.import('node_modules/mobiledoc-kit/dist/amd/mobiledoc-kit.js');
app.import('node_modules/mobiledoc-kit/dist/amd/mobiledoc-kit.map');
app.import('node_modules/simplemde/debug/simplemde.js');
app.import('node_modules/reframe.js/dist/noframe.es.js', {
using: [
{transformation: 'es6', as: 'noframe.js'}
]
});

// pull things we rely on via lazy-loading into the test-support.js file so
// that tests don't break when running via http://localhost:4200/tests
Expand Down
145 changes: 110 additions & 35 deletions lib/koenig-editor/addon/components/koenig-card-embed.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Component from '@ember/component';
import layout from '../templates/components/koenig-card-embed';
import noframe from 'noframe.js';
import {NO_CURSOR_MOVEMENT} from './koenig-editor';
import {isBlank} from '@ember/utils';
import {run} from '@ember/runloop';
Expand Down Expand Up @@ -40,10 +41,18 @@ export default Component.extend({

didInsertElement() {
this._super(...arguments);
this._loadPayloadScript();
this._populateIframe();
this._focusInput();
},

willDestroyElement() {
this._super(...arguments);

if (this._iframeMutationObserver) {
this._iframeMutationObserver.disconnect();
}
},

actions: {
onDeselect() {
if (this.payload.url && !this.payload.html && !this.hasError) {
Expand Down Expand Up @@ -113,7 +122,7 @@ export default Component.extend({
set(this.payload, 'type', response.type);
this.saveCard(this.payload, false);

run.schedule('afterRender', this, this._loadPayloadScript);
run.schedule('afterRender', this, this._populateIframe);
} catch (err) {
this.set('hasError', true);
}
Expand All @@ -127,42 +136,108 @@ export default Component.extend({
}
},

// some oembeds will have a script tag but it won't automatically run
// due to the way Ember renders the card components. Grab the script
// element and push a new one to force the browser to download+run it
_loadPayloadScript() {
let oldScript = this.element.querySelector('script');
if (oldScript) {
let parent = oldScript.parentElement;
let newScript = document.createElement('script');
newScript.type = 'text/javascript';

if (oldScript.src) {
// hide the original embed html to avoid ugly transitions as the
// script runs (at least on reasonably good network and cpu)
let embedElement = this.element.querySelector('[data-kg-embed]');
embedElement.style.display = 'none';

newScript.src = oldScript.src;

// once the script has loaded, wait a little while for it to do it's
// thing before making everything visible again
newScript.onload = run.bind(this, function () {
run.later(this, function () {
embedElement.style.display = null;
}, 500);
});

newScript.onerror = run.bind(this, function () {
embedElement.style.display = null;
});
} else {
newScript.innerHTML = oldScript.innerHTML;
_populateIframe() {
let iframe = this.element.querySelector('iframe');
if (iframe) {
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(this.payload.html);
iframe.contentWindow.document.close();

iframe.contentDocument.body.style.display = 'flex';
iframe.contentDocument.body.style.justifyContent = 'center';

let nestedIframe = iframe.contentDocument.body.firstChild;
if (nestedIframe.nodeName === 'IFRAME') {
noframe(nestedIframe, '[data-kg-embed]');
this._resizeIframe(iframe);
}

oldScript.remove();
parent.appendChild(newScript);
this._iframeResizeHandler = run.bind(this, this._resizeIframe, iframe);
this._iframeMutationObserver = this._createMutationObserver(
iframe.contentWindow.document,
this._iframeResizeHandler
);
}
},

_createMutationObserver(target, callback) {
function addImageLoadListeners(mutation) {
function addImageLoadListener(element) {
if (element.complete === false) {
element.addEventListener('load', imageEventTriggered, false);
element.addEventListener('error', imageEventTriggered, false);
imageElements.push(element);
}
}

if (mutation.type === 'attributes' && mutation.attributeName === 'src') {
addImageLoadListener(mutation.target);
} else if (mutation.type === 'childList') {
Array.prototype.forEach.call(
mutation.target.querySelectorAll('img'),
addImageLoadListener
);
}
}

function removeFromElements(element) {
imageElements.splice(imageElements.indexOf(element), 1);
}

function removeImageLoadListener(element) {
element.removeEventListener('load', imageEventTriggered, false);
element.removeEventListener('error', imageEventTriggered, false);
removeFromElements(element);
}

function imageEventTriggered(event) {
removeImageLoadListener(event.target);
callback();
}

function mutationObserved(mutations) {
callback();

// deal with async image loads when tags are injected into the page
mutations.forEach(addImageLoadListeners);
}

function createMutationObserver(target) {
let config = {
attributes: true,
attributeOldValue: false,
characterData: true,
characterDataOldValue: false,
childList: true,
subtree: true
};

let observer = new MutationObserver(mutationObserved);
observer.observe(target, config); // eslint-disable-line ghost/ember/no-observers
return observer;
}

let imageElements = [];
let observer = createMutationObserver(target);

return {
disconnect() {
if ('disconnect' in observer) {
observer.disconnect(); // eslint-disable-line ghost/ember/no-observers
imageElements.forEach(removeImageLoadListener);
}
}
};
},

_resizeIframe(iframe) {
run.debounce(this, this.__debouncedResizeIframe, iframe, 16);
},

__debouncedResizeIframe(iframe) {
iframe.style.height = null;
let height = iframe.contentDocument.scrollingElement.scrollHeight;
iframe.style.height = `${height}px`;
},

_deleteIfEmpty() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
{{#if payload.html}}
<div class="kg-card-hover">
<div class="koenig-embed-{{payload.type}} flex justify-center relative" data-kg-embed>
{{{payload.html}}}
<iframe class="bn miw-100" scrolling="no"></iframe>
<div class="koenig-card-click-overlay ba b--white" data-kg-overlay></div>
</div>

Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"ember-cli-cjs-transform": "1.3.0",
"ember-cli-code-coverage": "0.4.2",
"ember-cli-dependency-checker": "2.1.1",
"ember-cli-es6-transform": "^0.0.3",
"ember-cli-eslint": "4.2.3",
"ember-cli-ghost-spirit": "0.0.6",
"ember-cli-htmlbars": "2.0.3",
Expand Down Expand Up @@ -115,6 +116,7 @@
"mobiledoc-kit": "0.10.21",
"normalize.css": "3.0.3",
"password-generator": "2.2.0",
"reframe.js": "2.2.1",
"simplemde": "https://github.com/kevinansfield/simplemde-markdown-editor.git#ghost",
"testem": "2.6.0",
"top-gh-contribs": "2.0.4",
Expand Down
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3369,6 +3369,12 @@ ember-cli-dependency-checker@2.1.1:
resolve "^1.5.0"
semver "^5.3.0"

ember-cli-es6-transform@^0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/ember-cli-es6-transform/-/ember-cli-es6-transform-0.0.3.tgz#99238305c72f533cc1cd3c85b15c6d7a842b8fdf"
dependencies:
ember-cli-babel "^6.6.0"

ember-cli-eslint@4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/ember-cli-eslint/-/ember-cli-eslint-4.2.3.tgz#2844d3f5e8184f19b2d7132ba99eb0b370b55598"
Expand Down Expand Up @@ -8632,6 +8638,10 @@ reduce-css-calc@^2.0.0:
css-unit-converter "^1.1.1"
postcss-value-parser "^3.3.0"

reframe.js@2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/reframe.js/-/reframe.js-2.2.1.tgz#c4df52c815152b57458843d53d0246cb6b954d59"

regenerate@^1.2.1:
version "1.4.0"
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
Expand Down

0 comments on commit 30a362c

Please sign in to comment.