mirror of
https://github.com/TryGhost/Ghost-Admin.git
synced 2023-12-14 02:33:04 +01:00
🐛 Koenig - Fix embedding of multiple FB Videos
refs https://github.com/TryGhost/Ghost/issues/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
This commit is contained in:
parent
f9b08d8d64
commit
57a66f7cdc
|
@ -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.js');
|
||||||
app.import('node_modules/mobiledoc-kit/dist/amd/mobiledoc-kit.map');
|
app.import('node_modules/mobiledoc-kit/dist/amd/mobiledoc-kit.map');
|
||||||
app.import('node_modules/simplemde/debug/simplemde.js');
|
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
|
// 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
|
// that tests don't break when running via http://localhost:4200/tests
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import Component from '@ember/component';
|
import Component from '@ember/component';
|
||||||
import layout from '../templates/components/koenig-card-embed';
|
import layout from '../templates/components/koenig-card-embed';
|
||||||
|
import noframe from 'noframe.js';
|
||||||
import {NO_CURSOR_MOVEMENT} from './koenig-editor';
|
import {NO_CURSOR_MOVEMENT} from './koenig-editor';
|
||||||
import {isBlank} from '@ember/utils';
|
import {isBlank} from '@ember/utils';
|
||||||
import {run} from '@ember/runloop';
|
import {run} from '@ember/runloop';
|
||||||
|
@ -40,10 +41,22 @@ export default Component.extend({
|
||||||
|
|
||||||
didInsertElement() {
|
didInsertElement() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
this._loadPayloadScript();
|
this._populateIframe();
|
||||||
this._focusInput();
|
this._focusInput();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
willDestroyElement() {
|
||||||
|
this._super(...arguments);
|
||||||
|
|
||||||
|
run.cancel(this._resizeDebounce);
|
||||||
|
|
||||||
|
if (this._iframeMutationObserver) {
|
||||||
|
this._iframeMutationObserver.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('resize', this._windowResizeHandler);
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
onDeselect() {
|
onDeselect() {
|
||||||
if (this.payload.url && !this.payload.html && !this.hasError) {
|
if (this.payload.url && !this.payload.html && !this.hasError) {
|
||||||
|
@ -113,7 +126,7 @@ export default Component.extend({
|
||||||
set(this.payload, 'type', response.type);
|
set(this.payload, 'type', response.type);
|
||||||
this.saveCard(this.payload, false);
|
this.saveCard(this.payload, false);
|
||||||
|
|
||||||
run.schedule('afterRender', this, this._loadPayloadScript);
|
run.schedule('afterRender', this, this._populateIframe);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.set('hasError', true);
|
this.set('hasError', true);
|
||||||
}
|
}
|
||||||
|
@ -127,44 +140,118 @@ export default Component.extend({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// some oembeds will have a script tag but it won't automatically run
|
_populateIframe() {
|
||||||
// due to the way Ember renders the card components. Grab the script
|
let iframe = this.element.querySelector('iframe');
|
||||||
// element and push a new one to force the browser to download+run it
|
if (iframe) {
|
||||||
_loadPayloadScript() {
|
iframe.contentWindow.document.open();
|
||||||
let oldScript = this.element.querySelector('script');
|
iframe.contentWindow.document.write(this.payload.html);
|
||||||
if (oldScript) {
|
iframe.contentWindow.document.close();
|
||||||
let parent = oldScript.parentElement;
|
|
||||||
let newScript = document.createElement('script');
|
|
||||||
newScript.type = 'text/javascript';
|
|
||||||
|
|
||||||
if (oldScript.src) {
|
iframe.contentDocument.body.style.display = 'flex';
|
||||||
// hide the original embed html to avoid ugly transitions as the
|
iframe.contentDocument.body.style.margin = '0';
|
||||||
// script runs (at least on reasonably good network and cpu)
|
iframe.contentDocument.body.style.justifyContent = 'center';
|
||||||
let embedElement = this.element.querySelector('[data-kg-embed]');
|
|
||||||
embedElement.style.display = 'none';
|
|
||||||
|
|
||||||
newScript.src = oldScript.src;
|
let nestedIframe = iframe.contentDocument.body.firstChild;
|
||||||
|
if (nestedIframe.nodeName === 'IFRAME') {
|
||||||
// once the script has loaded, wait a little while for it to do it's
|
noframe(nestedIframe, '[data-kg-embed]');
|
||||||
// thing before making everything visible again
|
this._resizeIframe(iframe);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
oldScript.remove();
|
this._iframeResizeHandler = run.bind(this, this._resizeIframe, iframe);
|
||||||
parent.appendChild(newScript);
|
this._iframeMutationObserver = this._createMutationObserver(
|
||||||
|
iframe.contentWindow.document,
|
||||||
|
this._iframeResizeHandler
|
||||||
|
);
|
||||||
|
|
||||||
|
this._setupWindowResizeHandler(iframe);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_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) {
|
||||||
|
this._resizeDebounce = run.debounce(this, this.__debouncedResizeIframe, iframe, 66);
|
||||||
|
},
|
||||||
|
|
||||||
|
__debouncedResizeIframe(iframe) {
|
||||||
|
iframe.style.height = null;
|
||||||
|
let height = iframe.contentDocument.scrollingElement.scrollHeight;
|
||||||
|
iframe.style.height = `${height}px`;
|
||||||
|
},
|
||||||
|
|
||||||
|
_setupWindowResizeHandler(iframe) {
|
||||||
|
this._windowResizeHandler = run.bind(this, this._resizeIframe, iframe);
|
||||||
|
window.addEventListener('resize', this._windowResizeHandler, {passive: true});
|
||||||
|
},
|
||||||
|
|
||||||
_deleteIfEmpty() {
|
_deleteIfEmpty() {
|
||||||
if (isBlank(this.payload.html) && !this.convertUrl.isRunning && !this.hasError) {
|
if (isBlank(this.payload.html) && !this.convertUrl.isRunning && !this.hasError) {
|
||||||
this.deleteCard(NO_CURSOR_MOVEMENT);
|
this.deleteCard(NO_CURSOR_MOVEMENT);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{#koenig-card
|
{{#koenig-card
|
||||||
class="flex flex-column"
|
class="flex flex-column nl2 nr2"
|
||||||
isSelected=isSelected
|
isSelected=isSelected
|
||||||
isEditing=isEditing
|
isEditing=isEditing
|
||||||
selectCard=(action selectCard)
|
selectCard=(action selectCard)
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
{{#if payload.html}}
|
{{#if payload.html}}
|
||||||
<div class="kg-card-hover">
|
<div class="kg-card-hover">
|
||||||
<div class="koenig-embed-{{payload.type}} flex justify-center relative" data-kg-embed>
|
<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 class="koenig-card-click-overlay ba b--white" data-kg-overlay></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -54,4 +54,4 @@
|
||||||
onkeydown={{action "urlKeydown"}}>
|
onkeydown={{action "urlKeydown"}}>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/koenig-card}}
|
{{/koenig-card}}
|
||||||
|
|
|
@ -52,6 +52,7 @@
|
||||||
"ember-cli-cjs-transform": "1.3.0",
|
"ember-cli-cjs-transform": "1.3.0",
|
||||||
"ember-cli-code-coverage": "0.4.2",
|
"ember-cli-code-coverage": "0.4.2",
|
||||||
"ember-cli-dependency-checker": "2.1.1",
|
"ember-cli-dependency-checker": "2.1.1",
|
||||||
|
"ember-cli-es6-transform": "^0.0.3",
|
||||||
"ember-cli-eslint": "4.2.3",
|
"ember-cli-eslint": "4.2.3",
|
||||||
"ember-cli-ghost-spirit": "0.0.6",
|
"ember-cli-ghost-spirit": "0.0.6",
|
||||||
"ember-cli-htmlbars": "2.0.3",
|
"ember-cli-htmlbars": "2.0.3",
|
||||||
|
@ -115,6 +116,7 @@
|
||||||
"mobiledoc-kit": "0.10.21",
|
"mobiledoc-kit": "0.10.21",
|
||||||
"normalize.css": "3.0.3",
|
"normalize.css": "3.0.3",
|
||||||
"password-generator": "2.2.0",
|
"password-generator": "2.2.0",
|
||||||
|
"reframe.js": "2.2.1",
|
||||||
"simplemde": "https://github.com/kevinansfield/simplemde-markdown-editor.git#ghost",
|
"simplemde": "https://github.com/kevinansfield/simplemde-markdown-editor.git#ghost",
|
||||||
"testem": "2.6.0",
|
"testem": "2.6.0",
|
||||||
"top-gh-contribs": "2.0.4",
|
"top-gh-contribs": "2.0.4",
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -3369,6 +3369,12 @@ ember-cli-dependency-checker@2.1.1:
|
||||||
resolve "^1.5.0"
|
resolve "^1.5.0"
|
||||||
semver "^5.3.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:
|
ember-cli-eslint@4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/ember-cli-eslint/-/ember-cli-eslint-4.2.3.tgz#2844d3f5e8184f19b2d7132ba99eb0b370b55598"
|
resolved "https://registry.yarnpkg.com/ember-cli-eslint/-/ember-cli-eslint-4.2.3.tgz#2844d3f5e8184f19b2d7132ba99eb0b370b55598"
|
||||||
|
@ -8632,6 +8638,10 @@ reduce-css-calc@^2.0.0:
|
||||||
css-unit-converter "^1.1.1"
|
css-unit-converter "^1.1.1"
|
||||||
postcss-value-parser "^3.3.0"
|
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:
|
regenerate@^1.2.1:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
|
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
|
||||||
|
|
Loading…
Reference in a new issue