fix(creator-studio): delete selected hidden blocks reliably
This commit is contained in:
1 parent
304c59c8f3
commit
d391b0fd00
4 files changed
+124
-21
No files matched your search
@@ -20,6 +20,12 @@ const serializeCompiled = ts.transpileModule(serializeSource, {
|
||||
}).outputText;
|
||||
const serializer = {};
|
||||
new Function('exports', serializeCompiled)(serializer);
|
||||
const markupSource = fs.readFileSync(path.join(__dirname, '../src/utils/block-markup.ts'), 'utf8');
|
||||
const markupCompiled = ts.transpileModule(markupSource, {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
|
||||
}).outputText;
|
||||
const markup = {};
|
||||
new Function('exports', 'require', markupCompiled)(markup, () => ({ __: (value) => value }));
|
||||
|
||||
function fixture(html, nodeSelector, offset = 0) {
|
||||
const dom = new JSDOM(`<!doctype html><body contenteditable="true">${html}</body>`);
|
||||
@@ -40,6 +46,10 @@ function fixture(html, nodeSelector, offset = 0) {
|
||||
range.setStart(target, at);
|
||||
range.collapse(true);
|
||||
},
|
||||
select: (target) => {
|
||||
range = dom.window.document.createRange();
|
||||
range.selectNode(target);
|
||||
},
|
||||
},
|
||||
dom: { remove: (target) => target.remove() },
|
||||
undoManager: { transact: (action) => { events.push('undo'); action(); } },
|
||||
@@ -128,14 +138,76 @@ test('a real line break inside a paragraph is not a block boundary', () => {
|
||||
assert.equal(f.body.querySelectorAll('hr').length, 1);
|
||||
});
|
||||
|
||||
test('nested quote and hidden-content editing boundaries remove the whole outer block', () => {
|
||||
for (const html of [
|
||||
'<div class="quote_q" contenteditable="false"><div contenteditable="true"><p>quote</p></div></div>',
|
||||
'<div class="tinymce-hide"><p class="hide-before">[vip]</p><div contenteditable="true"><p>secret</p></div><p class="hide-after">[/vip]</p></div>',
|
||||
]) {
|
||||
const f = fixture(`${html}<p>next</p>`, '[contenteditable="true"] p', 0);
|
||||
assert.equal(f.press('Backspace').prevented, true);
|
||||
assert.equal(f.body.innerHTML, '<p>next</p>');
|
||||
test('a quote can be removed from its inner editing boundary', () => {
|
||||
const f = fixture('<div class="quote_q" contenteditable="false"><div contenteditable="true"><p>quote</p></div></div><p>next</p>', '[contenteditable="true"] p', 0);
|
||||
assert.equal(f.press('Backspace').prevented, true);
|
||||
assert.equal(f.body.innerHTML, '<p>next</p>');
|
||||
});
|
||||
|
||||
const hiddenBlock = '<div class="tinymce-hide" contenteditable="false"><p class="hide-before">[vip]</p><div contenteditable="true"><p>secret</p></div><p class="hide-after">[/vip]</p></div>';
|
||||
|
||||
test('new hidden blocks keep shortcodes outside the editable content', () => {
|
||||
const dom = new JSDOM('<!doctype html><body></body>');
|
||||
dom.window.document.body.innerHTML = markup.hiddenContentMarkup('vip', '<p>secret</p>');
|
||||
const block = dom.window.document.body.querySelector('.tinymce-hide');
|
||||
assert.equal(block.getAttribute('contenteditable'), 'false');
|
||||
assert.equal(block.querySelector(':scope > div').getAttribute('contenteditable'), 'true');
|
||||
assert.equal(block.querySelector('.hide-before').textContent, '[vip]');
|
||||
assert.equal(block.querySelector('.hide-after').textContent, '[/vip]');
|
||||
});
|
||||
|
||||
test('Backspace after a hidden block selects it first, then removes it in one more press', () => {
|
||||
const f = fixture(`${hiddenBlock}<p><br data-mce-bogus="1"></p>`, 'body > p', 0);
|
||||
assert.equal(f.press('Backspace').prevented, true);
|
||||
assert.equal(f.body.querySelectorAll('.tinymce-hide').length, 1);
|
||||
assert.equal(f.editor.selection.getRng().startContainer, f.body);
|
||||
assert.equal(f.editor.selection.getRng().endOffset, 1);
|
||||
assert.equal(f.press('Backspace').prevented, true);
|
||||
assert.equal(f.body.querySelectorAll('.tinymce-hide').length, 0);
|
||||
assert.equal(f.body.firstElementChild.tagName, 'P');
|
||||
assert.deepEqual(f.events, ['node', 'undo', 'node', 'change']);
|
||||
});
|
||||
|
||||
test('editing hidden text stays native; Backspace at its start selects the whole block', () => {
|
||||
const f = fixture(`${hiddenBlock}<p>next</p>`, '.tinymce-hide [contenteditable="true"] p', 0);
|
||||
const text = f.body.querySelector('.tinymce-hide [contenteditable="true"] p').firstChild;
|
||||
f.editor.selection.setCursorLocation(text, 3);
|
||||
assert.equal(f.press('Backspace').prevented, false);
|
||||
assert.equal(f.body.querySelector('.tinymce-hide [contenteditable="true"] p').textContent, 'secret');
|
||||
f.editor.selection.setCursorLocation(text, 0);
|
||||
assert.equal(f.press('Backspace').prevented, true);
|
||||
assert.equal(f.editor.selection.getRng().endOffset, 1);
|
||||
assert.equal(f.press('Backspace').prevented, true);
|
||||
assert.equal(f.body.innerHTML, '<p>next</p>');
|
||||
});
|
||||
|
||||
test('a manually selected hidden block deletes without repeated native key presses', () => {
|
||||
const f = fixture(hiddenBlock, 'body', 0);
|
||||
f.editor.selection.select(f.body.firstElementChild);
|
||||
assert.equal(f.press('Delete').prevented, true);
|
||||
assert.equal(f.body.querySelector('.tinymce-hide'), null);
|
||||
assert.equal(f.body.firstElementChild.tagName, 'P');
|
||||
});
|
||||
|
||||
test('selecting only hidden-block text does not delete its container', () => {
|
||||
const f = fixture(hiddenBlock, '.tinymce-hide [contenteditable="true"] p', 0);
|
||||
const range = f.editor.selection.getRng();
|
||||
range.selectNodeContents(f.body.querySelector('.tinymce-hide [contenteditable="true"] p'));
|
||||
assert.equal(f.press('Backspace').prevented, false);
|
||||
assert.equal(f.body.querySelectorAll('.tinymce-hide').length, 1);
|
||||
});
|
||||
|
||||
test('hydration restores the hidden block editing boundary', () => {
|
||||
const dom = new JSDOM('<!doctype html><body></body>');
|
||||
const previousDocument = global.document;
|
||||
global.document = dom.window.document;
|
||||
try {
|
||||
dom.window.document.body.innerHTML = serializer.hydrateBlocks(hiddenBlock.replace(' contenteditable="false"', '').replace(' contenteditable="true"', ''));
|
||||
const block = dom.window.document.body.querySelector('.tinymce-hide');
|
||||
assert.equal(block.getAttribute('contenteditable'), 'false');
|
||||
assert.equal(block.querySelector(':scope > div').getAttribute('contenteditable'), 'true');
|
||||
} finally {
|
||||
global.document = previousDocument;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user