parent
bb0a7e96d8
commit
99071c14e4
103
cypress/integration/markdown_widget_hotkeys_spec.js
Normal file
103
cypress/integration/markdown_widget_hotkeys_spec.js
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import '../utils/dismiss-local-backup';
|
||||||
|
import {HOT_KEY_MAP} from "../utils/constants";
|
||||||
|
const headingNumberToWord = ['', 'one', 'two', 'three', 'four', 'five', 'six'];
|
||||||
|
const isMac = Cypress.platform === 'darwin';
|
||||||
|
const modifierKey = isMac ? '{meta}' : '{ctrl}';
|
||||||
|
const replaceMod = (str) => str.replace(/mod\+/g, modifierKey).replace(/shift\+/g, '{shift}');
|
||||||
|
|
||||||
|
describe('Markdown widget', () => {
|
||||||
|
describe('hot keys', () => {
|
||||||
|
before(() => {
|
||||||
|
Cypress.config('defaultCommandTimeout', 4000);
|
||||||
|
cy.task('setupBackend', { backend: 'test' });
|
||||||
|
cy.loginAndNewPost();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.clearMarkdownEditorContent();
|
||||||
|
cy.focused()
|
||||||
|
.type('foo')
|
||||||
|
.setSelection('foo').as('selection');
|
||||||
|
});
|
||||||
|
|
||||||
|
after(() => {
|
||||||
|
cy.task('teardownBackend', { backend: 'test' });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('bold', () => {
|
||||||
|
it('pressing mod+b bolds the text', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['bold']))
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<strong>foo</strong>
|
||||||
|
</p>
|
||||||
|
`)
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['bold']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('italic', () => {
|
||||||
|
it('pressing mod+i italicizes the text', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['italic']))
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<em>foo</em>
|
||||||
|
</p>
|
||||||
|
`)
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['italic']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('strikethrough', () => {
|
||||||
|
it('pressing mod+shift+s displays a strike through the text', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['strikethrough']))
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<s>foo</s>
|
||||||
|
</p>
|
||||||
|
`).type(replaceMod(HOT_KEY_MAP['strikethrough']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('code', () => {
|
||||||
|
it('pressing mod+shift+c displays a code block around the text', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['code']))
|
||||||
|
.confirmMarkdownEditorContent(`
|
||||||
|
<p>
|
||||||
|
<code>foo</code>
|
||||||
|
</p>
|
||||||
|
`).type(replaceMod(HOT_KEY_MAP['code']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('link', () => {
|
||||||
|
before(() => {
|
||||||
|
cy.window().then((win) => {
|
||||||
|
cy.stub(win, 'prompt').returns('https://google.com');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('pressing mod+k transforms the text to a link', () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['link']))
|
||||||
|
.confirmMarkdownEditorContent('<p><a>foo</a></p>')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP['link']));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('headings', () => {
|
||||||
|
for (let i = 1; i <= 6; i++) {
|
||||||
|
it(`pressing mod+${i} transforms the text to a heading`, () => {
|
||||||
|
cy.get('@selection')
|
||||||
|
.type(replaceMod(HOT_KEY_MAP[`heading-${headingNumberToWord[i]}`]))
|
||||||
|
.confirmMarkdownEditorContent(`<h${i}>foo</h${i}>`)
|
||||||
|
.type(replaceMod(HOT_KEY_MAP[`heading-${headingNumberToWord[i]}`]))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -295,11 +295,14 @@ Cypress.Commands.add('confirmMarkdownEditorContent', expectedDomString => {
|
|||||||
// Slate makes the following representations:
|
// Slate makes the following representations:
|
||||||
// - blank line: 2 BOM's + <br>
|
// - blank line: 2 BOM's + <br>
|
||||||
// - blank element (placed inside empty elements): 1 BOM + <br>
|
// - blank element (placed inside empty elements): 1 BOM + <br>
|
||||||
// We replace to represent a blank line as a single <br>, and remove the
|
// - inline element (e.g. link tag <a>) are wrapped with BOM characters (https://github.com/ianstormtaylor/slate/issues/2722)
|
||||||
// contents of elements that are actually empty.
|
// We replace to represent a blank line as a single <br>, remove the
|
||||||
|
// contents of elements that are actually empty, and remove BOM characters wrapping <a> tags
|
||||||
const actualDomString = toPlainTree(element.innerHTML)
|
const actualDomString = toPlainTree(element.innerHTML)
|
||||||
.replace(/\uFEFF\uFEFF<br>/g, '<br>')
|
.replace(/\uFEFF\uFEFF<br>/g, '<br>')
|
||||||
.replace(/\uFEFF<br>/g, '');
|
.replace(/\uFEFF<br>/g, '')
|
||||||
|
.replace(/\uFEFF<a>/g, '<a>')
|
||||||
|
.replace(/<\/a>\uFEFF/g, '</a>');
|
||||||
expect(actualDomString).toEqual(oneLineTrim(expectedDomString));
|
expect(actualDomString).toEqual(oneLineTrim(expectedDomString));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -335,7 +338,7 @@ function removeSlateArtifacts() {
|
|||||||
delete node.properties;
|
delete node.properties;
|
||||||
|
|
||||||
// remove slate padding spans to simplify test cases
|
// remove slate padding spans to simplify test cases
|
||||||
if (['h1', 'p'].includes(node.tagName)) {
|
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p'].includes(node.tagName)) {
|
||||||
node.children = node.children.flatMap(getActualBlockChildren);
|
node.children = node.children.flatMap(getActualBlockChildren);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -19,6 +19,19 @@ const notifications = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
const HOT_KEY_MAP = {
|
||||||
|
'bold': 'mod+b',
|
||||||
|
'code': 'mod+shift+c',
|
||||||
|
'italic': 'mod+i',
|
||||||
|
'strikethrough': 'mod+shift+s',
|
||||||
|
'heading-one': 'mod+1',
|
||||||
|
'heading-two': 'mod+2',
|
||||||
|
'heading-three': 'mod+3',
|
||||||
|
'heading-four': 'mod+4',
|
||||||
|
'heading-five': 'mod+5',
|
||||||
|
'heading-six': 'mod+6',
|
||||||
|
'link': 'mod+k',
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
workflowStatus,
|
workflowStatus,
|
||||||
@ -27,4 +40,5 @@ module.exports = {
|
|||||||
setting2,
|
setting2,
|
||||||
notifications,
|
notifications,
|
||||||
publishTypes,
|
publishTypes,
|
||||||
|
HOT_KEY_MAP
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
import isHotkey from 'is-hotkey';
|
||||||
|
|
||||||
|
export const HOT_KEY_MAP = {
|
||||||
|
bold: 'mod+b',
|
||||||
|
code: 'mod+shift+c',
|
||||||
|
italic: 'mod+i',
|
||||||
|
strikethrough: 'mod+shift+s',
|
||||||
|
'heading-one': 'mod+1',
|
||||||
|
'heading-two': 'mod+2',
|
||||||
|
'heading-three': 'mod+3',
|
||||||
|
'heading-four': 'mod+4',
|
||||||
|
'heading-five': 'mod+5',
|
||||||
|
'heading-six': 'mod+6',
|
||||||
|
link: 'mod+k',
|
||||||
|
};
|
||||||
|
|
||||||
|
const Hotkey = (key, fn) => {
|
||||||
|
return {
|
||||||
|
onKeyDown(event, editor, next) {
|
||||||
|
if (!isHotkey(key, event)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
editor.command(fn);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Hotkey;
|
@ -12,6 +12,7 @@ import Link from './Link';
|
|||||||
import ForceInsert from './ForceInsert';
|
import ForceInsert from './ForceInsert';
|
||||||
import Shortcode from './Shortcode';
|
import Shortcode from './Shortcode';
|
||||||
import { SLATE_DEFAULT_BLOCK_TYPE as defaultType } from '../../types';
|
import { SLATE_DEFAULT_BLOCK_TYPE as defaultType } from '../../types';
|
||||||
|
import Hotkey, { HOT_KEY_MAP } from './Hotkey';
|
||||||
|
|
||||||
const plugins = ({ getAsset, resolveWidget }) => [
|
const plugins = ({ getAsset, resolveWidget }) => [
|
||||||
{
|
{
|
||||||
@ -22,6 +23,17 @@ const plugins = ({ getAsset, resolveWidget }) => [
|
|||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Hotkey(HOT_KEY_MAP['bold'], e => e.toggleMark('bold')),
|
||||||
|
Hotkey(HOT_KEY_MAP['code'], e => e.toggleMark('code')),
|
||||||
|
Hotkey(HOT_KEY_MAP['italic'], e => e.toggleMark('italic')),
|
||||||
|
Hotkey(HOT_KEY_MAP['strikethrough'], e => e.toggleMark('strikethrough')),
|
||||||
|
Hotkey(HOT_KEY_MAP['heading-one'], e => e.toggleBlock('heading-one')),
|
||||||
|
Hotkey(HOT_KEY_MAP['heading-two'], e => e.toggleBlock('heading-two')),
|
||||||
|
Hotkey(HOT_KEY_MAP['heading-three'], e => e.toggleBlock('heading-three')),
|
||||||
|
Hotkey(HOT_KEY_MAP['heading-four'], e => e.toggleBlock('heading-four')),
|
||||||
|
Hotkey(HOT_KEY_MAP['heading-five'], e => e.toggleBlock('heading-five')),
|
||||||
|
Hotkey(HOT_KEY_MAP['heading-six'], e => e.toggleBlock('heading-six')),
|
||||||
|
Hotkey(HOT_KEY_MAP['link'], e => e.toggleLink(() => window.prompt('Enter the URL of the link'))),
|
||||||
CommandsAndQueries({ defaultType }),
|
CommandsAndQueries({ defaultType }),
|
||||||
QuoteBlock({ defaultType, type: 'quote' }),
|
QuoteBlock({ defaultType, type: 'quote' }),
|
||||||
ListPlugin({ defaultType, unorderedListType: 'bulleted-list', orderedListType: 'numbered-list' }),
|
ListPlugin({ defaultType, unorderedListType: 'bulleted-list', orderedListType: 'numbered-list' }),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user