0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

🐝 Strip HTML from title (#665)

closes https://github.com/TryGhost/Ghost/issues/8353
- if a user pastes or inserts HTML into the title in some other way (modifying the dom, having a plugin mutate the dom, etc) then strip the HTML and insure that the title is always unstyled and plain text
This commit is contained in:
Ryan McCarvill 2017-04-24 23:37:30 +12:00 committed by Kevin Ansfield
parent 535f24545c
commit 99000a74fa
3 changed files with 48 additions and 15 deletions

View file

@ -115,12 +115,19 @@ export default Component.extend({
title.addClass('no-content'); title.addClass('no-content');
} }
let {textContent} = title[0]; // eslint-disable-line // there is no consistency in how characters like nbsp and zwd are handled across browsers
// // sanity check if there is formatting reset it. // so we replace every whitespace character with a ' '
// if (title[0].innerHTML !== textContent && title[0].innerHTML) { // note: this means that we can't have tabs in the title.
// title[0].innerHTML = textContent; let textContent = title[0].textContent.replace(/\s/g, ' ');
// // todo: retain the range position. let innerHTML = title[0].innerHTML.replace(/( |\s)/g, ' ');
// }
// sanity check if there is formatting reset it.
if (innerHTML && innerHTML !== textContent) {
// run in next runloop so that we don't get stuck in infinite loops.
run.next(() => {
title[0].innerHTML = textContent;
});
}
if (this.get('val') !== textContent) { if (this.get('val') !== textContent) {
this.set('_cachedVal', textContent); this.set('_cachedVal', textContent);
@ -211,14 +218,12 @@ export default Component.extend({
title.focus(); title.focus();
let selection = window.getSelection(); let selection = window.getSelection();
window.requestAnimationFrame(() => { run.next(() => {
run.join(() => {
if (selection.modify) { if (selection.modify) {
for (let i = 0; i < offset; i++) { for (let i = 0; i < offset; i++) {
selection.modify('move', 'forward', 'character'); selection.modify('move', 'forward', 'character');
} }
} }
}); });
});
} }
}); });

View file

@ -12,7 +12,7 @@ import {invalidateSession, authenticateSession} from 'ghost-admin/tests/helpers/
import Mirage from 'ember-cli-mirage'; import Mirage from 'ember-cli-mirage';
import sinon from 'sinon'; import sinon from 'sinon';
import testSelector from 'ember-test-selectors'; import testSelector from 'ember-test-selectors';
import {titleRendered} from '../helpers/editor-helpers'; import {titleRendered, replaceTitleHTML} from '../helpers/editor-helpers';
import moment from 'moment'; import moment from 'moment';
describe('Acceptance: Editor', function() { describe('Acceptance: Editor', function() {
@ -347,7 +347,7 @@ describe('Acceptance: Editor', function() {
).to.match(/Title cannot be longer than 150 characters/); ).to.match(/Title cannot be longer than 150 characters/);
}); });
it('if title is blank it correctly shows the placeholder', async function () { it('inserts a placeholder if the title is blank', async function () {
server.createList('post', 1); server.createList('post', 1);
// post id 1 is a draft, checking for draft behaviour now // post id 1 is a draft, checking for draft behaviour now
@ -369,6 +369,22 @@ describe('Acceptance: Editor', function() {
expect(title.hasClass('no-content')).to.be.false; expect(title.hasClass('no-content')).to.be.false;
}); });
it('removes HTML from the title.', async function () {
server.createList('post', 1);
// post id 1 is a draft, checking for draft behaviour now
await visit('/editor/1');
expect(currentURL(), 'currentURL')
.to.equal('/editor/1');
titleRendered();
let title = find('#gh-editor-title div');
await replaceTitleHTML('<div>TITLE&nbsp;&#09;&nbsp;&thinsp;&ensp;&emsp;TEST</div>&nbsp;');
expect(title.html()).to.equal('TITLE TEST ');
});
it('renders first countdown notification before scheduled time', async function () { it('renders first countdown notification before scheduled time', async function () {
let clock = sinon.useFakeTimers(moment().valueOf()); let clock = sinon.useFakeTimers(moment().valueOf());
let compareDate = moment().tz('Etc/UTC').add(4, 'minutes'); let compareDate = moment().tz('Etc/UTC').add(4, 'minutes');

View file

@ -1,5 +1,9 @@
import Ember from 'ember'; import Ember from 'ember';
import $ from 'jquery'; import $ from 'jquery';
import run from 'ember-runloop';
import wait from 'ember-test-helpers/wait';
import {findWithAssert} from 'ember-native-dom-helpers';
// polls the editor until it's started. // polls the editor until it's started.
export function editorRendered() { export function editorRendered() {
return Ember.Test.promise(function (resolve) { // eslint-disable-line return Ember.Test.promise(function (resolve) { // eslint-disable-line
@ -29,6 +33,14 @@ export function titleRendered() {
}); });
} }
// replaces the title text content with HTML and returns once the HTML has been placed.
// takes into account converting to plaintext.
export function replaceTitleHTML(HTML) {
let el = findWithAssert('#gh-editor-title div');
run(() => el.innerHTML = HTML);
return (window.wait || wait)();
}
// simulates text inputs into the editor, unfortunately the helper Ember helper functions // simulates text inputs into the editor, unfortunately the helper Ember helper functions
// don't work on content editable so we have to manipuate the text input event manager // don't work on content editable so we have to manipuate the text input event manager
// in mobiledoc-kit directly. This is a private API. // in mobiledoc-kit directly. This is a private API.