Commit 3f3067fc authored by Phil Hughes's avatar Phil Hughes

Merge branch '56989-reduce-bundle-size-by-loading-markdown-it-only-when-needed' into 'master'

Reduce Bundle Size by lazy loading markdown-it

Closes #56989

See merge request gitlab-org/gitlab-ce!24763
parents 7c54409f 81429f61
import $ from 'jquery';
import { DOMParser } from 'prosemirror-model';
import { getSelectedFragment } from '~/lib/utils/common_utils';
import schema from './schema';
import markdownSerializer from './serializer';
export class CopyAsGFM {
constructor() {
......@@ -39,9 +36,13 @@ export class CopyAsGFM {
div.appendChild(el.cloneNode(true));
const html = div.innerHTML;
clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', this.nodeToGFM(el));
clipboardData.setData('text/html', html);
CopyAsGFM.nodeToGFM(el)
.then(res => {
clipboardData.setData('text/plain', el.textContent);
clipboardData.setData('text/x-gfm', res);
clipboardData.setData('text/html', html);
})
.catch(() => {});
}
static pasteGFM(e) {
......@@ -137,11 +138,21 @@ export class CopyAsGFM {
}
static nodeToGFM(node) {
const wrapEl = document.createElement('div');
wrapEl.appendChild(node.cloneNode(true));
const doc = DOMParser.fromSchema(schema).parse(wrapEl);
return markdownSerializer.serialize(doc);
return Promise.all([
import(/* webpackChunkName: 'gfm_copy_extra' */ 'prosemirror-model'),
import(/* webpackChunkName: 'gfm_copy_extra' */ './schema'),
import(/* webpackChunkName: 'gfm_copy_extra' */ './serializer'),
])
.then(([prosemirrorModel, schema, markdownSerializer]) => {
const { DOMParser } = prosemirrorModel;
const wrapEl = document.createElement('div');
wrapEl.appendChild(node.cloneNode(true));
const doc = DOMParser.fromSchema(schema.default).parse(wrapEl);
const res = markdownSerializer.default.serialize(doc);
return res;
})
.catch(() => {});
}
}
......
......@@ -64,26 +64,30 @@ export default class ShortcutsIssuable extends Shortcuts {
const el = CopyAsGFM.transformGFMSelection(documentFragment.cloneNode(true));
const blockquoteEl = document.createElement('blockquote');
blockquoteEl.appendChild(el);
const text = CopyAsGFM.nodeToGFM(blockquoteEl);
if (text.trim() === '') {
return false;
}
// If replyField already has some content, add a newline before our quote
const separator = ($replyField.val().trim() !== '' && '\n\n') || '';
$replyField
.val((a, current) => `${current}${separator}${text}\n\n`)
.trigger('input')
.trigger('change');
// Trigger autosize
const event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
$replyField.get(0).dispatchEvent(event);
CopyAsGFM.nodeToGFM(blockquoteEl)
.then(text => {
if (text.trim() === '') {
return false;
}
// If replyField already has some content, add a newline before our quote
const separator = ($replyField.val().trim() !== '' && '\n\n') || '';
$replyField
.val((a, current) => `${current}${separator}${text}\n\n`)
.trigger('input')
.trigger('change');
// Trigger autosize
const event = document.createEvent('Event');
event.initEvent('autosize:update', true, false);
$replyField.get(0).dispatchEvent(event);
// Focus the input field
$replyField.focus();
// Focus the input field
$replyField.focus();
return false;
})
.catch(() => {});
return false;
}
......
......@@ -843,6 +843,7 @@ describe 'Copy as GFM', :js do
def verify(selector, gfm, target: nil)
html = html_for_selector(selector)
output_gfm = html_to_gfm(html, 'transformCodeSelection', target: target)
wait_for_requests
expect(output_gfm.strip).to eq(gfm.strip)
end
end
......@@ -861,6 +862,9 @@ describe 'Copy as GFM', :js do
def html_to_gfm(html, transformer = 'transformGFMSelection', target: nil)
js = <<~JS
(function(html) {
// Setting it off so the import already starts
window.CopyAsGFM.nodeToGFM(document.createElement('div'));
var transformer = window.CopyAsGFM[#{transformer.inspect}];
var node = document.createElement('div');
......@@ -875,9 +879,18 @@ describe 'Copy as GFM', :js do
node = transformer(node, target);
if (!node) return null;
return window.CopyAsGFM.nodeToGFM(node);
window.gfmCopytestRes = null;
window.CopyAsGFM.nodeToGFM(node)
.then((res) => {
window.gfmCopytestRes = res;
});
})("#{escape_javascript(html)}")
JS
page.evaluate_script(js)
page.execute_script(js)
loop until page.evaluate_script('window.gfmCopytestRes !== null')
page.evaluate_script('window.gfmCopytestRes')
end
end
import { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
describe('CopyAsGFM', () => {
describe('CopyAsGFM.pasteGFM', () => {
......@@ -79,27 +79,46 @@ describe('CopyAsGFM', () => {
return clipboardData;
};
beforeAll(done => {
initCopyAsGFM();
// Fake call to nodeToGfm so the import of lazy bundle happened
CopyAsGFM.nodeToGFM(document.createElement('div'))
.then(() => {
done();
})
.catch(done.fail);
});
beforeEach(() => spyOn(clipboardData, 'setData'));
describe('list handling', () => {
it('uses correct gfm for unordered lists', () => {
it('uses correct gfm for unordered lists', done => {
const selection = stubSelection('<li>List Item1</li><li>List Item2</li>\n', 'UL');
spyOn(window, 'getSelection').and.returnValue(selection);
simulateCopy();
const expectedGFM = '* List Item1\n\n* List Item2';
setTimeout(() => {
const expectedGFM = '* List Item1\n\n* List Item2';
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
done();
});
});
it('uses correct gfm for ordered lists', () => {
it('uses correct gfm for ordered lists', done => {
const selection = stubSelection('<li>List Item1</li><li>List Item2</li>\n', 'OL');
spyOn(window, 'getSelection').and.returnValue(selection);
simulateCopy();
const expectedGFM = '1. List Item1\n\n1. List Item2';
setTimeout(() => {
const expectedGFM = '1. List Item1\n\n1. List Item2';
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
expect(clipboardData.setData).toHaveBeenCalledWith('text/x-gfm', expectedGFM);
done();
});
});
});
});
......
......@@ -3,17 +3,26 @@
*/
import $ from 'jquery';
import initCopyAsGFM from '~/behaviors/markdown/copy_as_gfm';
import initCopyAsGFM, { CopyAsGFM } from '~/behaviors/markdown/copy_as_gfm';
import ShortcutsIssuable from '~/behaviors/shortcuts/shortcuts_issuable';
initCopyAsGFM();
const FORM_SELECTOR = '.js-main-target-form .js-vue-comment-form';
describe('ShortcutsIssuable', function() {
const fixtureName = 'snippets/show.html.raw';
preloadFixtures(fixtureName);
beforeAll(done => {
initCopyAsGFM();
// Fake call to nodeToGfm so the import of lazy bundle happened
CopyAsGFM.nodeToGFM(document.createElement('div'))
.then(() => {
done();
})
.catch(done.fail);
});
beforeEach(() => {
loadFixtures(fixtureName);
$('body').append(
......@@ -63,17 +72,22 @@ describe('ShortcutsIssuable', function() {
stubSelection('<p>Selected text.</p>');
});
it('leaves existing input intact', () => {
it('leaves existing input intact', done => {
$(FORM_SELECTOR).val('This text was already here.');
expect($(FORM_SELECTOR).val()).toBe('This text was already here.');
ShortcutsIssuable.replyWithSelectedText(true);
expect($(FORM_SELECTOR).val()).toBe('This text was already here.\n\n> Selected text.\n\n');
setTimeout(() => {
expect($(FORM_SELECTOR).val()).toBe(
'This text was already here.\n\n> Selected text.\n\n',
);
done();
});
});
it('triggers `input`', () => {
it('triggers `input`', done => {
let triggered = false;
$(FORM_SELECTOR).on('input', () => {
triggered = true;
......@@ -81,36 +95,48 @@ describe('ShortcutsIssuable', function() {
ShortcutsIssuable.replyWithSelectedText(true);
expect(triggered).toBe(true);
setTimeout(() => {
expect(triggered).toBe(true);
done();
});
});
it('triggers `focus`', () => {
it('triggers `focus`', done => {
const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
expect(spy).toHaveBeenCalled();
setTimeout(() => {
expect(spy).toHaveBeenCalled();
done();
});
});
});
describe('with a one-line selection', () => {
it('quotes the selection', () => {
it('quotes the selection', done => {
stubSelection('<p>This text has been selected.</p>');
ShortcutsIssuable.replyWithSelectedText(true);
expect($(FORM_SELECTOR).val()).toBe('> This text has been selected.\n\n');
setTimeout(() => {
expect($(FORM_SELECTOR).val()).toBe('> This text has been selected.\n\n');
done();
});
});
});
describe('with a multi-line selection', () => {
it('quotes the selected lines as a group', () => {
it('quotes the selected lines as a group', done => {
stubSelection(
'<p>Selected line one.</p>\n<p>Selected line two.</p>\n<p>Selected line three.</p>',
);
ShortcutsIssuable.replyWithSelectedText(true);
expect($(FORM_SELECTOR).val()).toBe(
'> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n',
);
setTimeout(() => {
expect($(FORM_SELECTOR).val()).toBe(
'> Selected line one.\n>\n> Selected line two.\n>\n> Selected line three.\n\n',
);
done();
});
});
});
......@@ -119,17 +145,23 @@ describe('ShortcutsIssuable', function() {
stubSelection('<p>Selected text.</p>', true);
});
it('does not add anything to the input', () => {
it('does not add anything to the input', done => {
ShortcutsIssuable.replyWithSelectedText(true);
expect($(FORM_SELECTOR).val()).toBe('');
setTimeout(() => {
expect($(FORM_SELECTOR).val()).toBe('');
done();
});
});
it('triggers `focus`', () => {
it('triggers `focus`', done => {
const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
expect(spy).toHaveBeenCalled();
setTimeout(() => {
expect(spy).toHaveBeenCalled();
done();
});
});
});
......@@ -138,20 +170,26 @@ describe('ShortcutsIssuable', function() {
stubSelection('<div class="md">Selected text.</div><p>Invalid selected text.</p>', true);
});
it('only adds the valid part to the input', () => {
it('only adds the valid part to the input', done => {
ShortcutsIssuable.replyWithSelectedText(true);
expect($(FORM_SELECTOR).val()).toBe('> Selected text.\n\n');
setTimeout(() => {
expect($(FORM_SELECTOR).val()).toBe('> Selected text.\n\n');
done();
});
});
it('triggers `focus`', () => {
it('triggers `focus`', done => {
const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
expect(spy).toHaveBeenCalled();
setTimeout(() => {
expect(spy).toHaveBeenCalled();
done();
});
});
it('triggers `input`', () => {
it('triggers `input`', done => {
let triggered = false;
$(FORM_SELECTOR).on('input', () => {
triggered = true;
......@@ -159,7 +197,10 @@ describe('ShortcutsIssuable', function() {
ShortcutsIssuable.replyWithSelectedText(true);
expect(triggered).toBe(true);
setTimeout(() => {
expect(triggered).toBe(true);
done();
});
});
});
......@@ -183,20 +224,26 @@ describe('ShortcutsIssuable', function() {
});
});
it('adds the quoted selection to the input', () => {
it('adds the quoted selection to the input', done => {
ShortcutsIssuable.replyWithSelectedText(true);
expect($(FORM_SELECTOR).val()).toBe('> *Selected text.*\n\n');
setTimeout(() => {
expect($(FORM_SELECTOR).val()).toBe('> *Selected text.*\n\n');
done();
});
});
it('triggers `focus`', () => {
it('triggers `focus`', done => {
const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
expect(spy).toHaveBeenCalled();
setTimeout(() => {
expect(spy).toHaveBeenCalled();
done();
});
});
it('triggers `input`', () => {
it('triggers `input`', done => {
let triggered = false;
$(FORM_SELECTOR).on('input', () => {
triggered = true;
......@@ -204,7 +251,10 @@ describe('ShortcutsIssuable', function() {
ShortcutsIssuable.replyWithSelectedText(true);
expect(triggered).toBe(true);
setTimeout(() => {
expect(triggered).toBe(true);
done();
});
});
});
......@@ -228,17 +278,23 @@ describe('ShortcutsIssuable', function() {
});
});
it('does not add anything to the input', () => {
it('does not add anything to the input', done => {
ShortcutsIssuable.replyWithSelectedText(true);
expect($(FORM_SELECTOR).val()).toBe('');
setTimeout(() => {
expect($(FORM_SELECTOR).val()).toBe('');
done();
});
});
it('triggers `focus`', () => {
it('triggers `focus`', done => {
const spy = spyOn(document.querySelector(FORM_SELECTOR), 'focus');
ShortcutsIssuable.replyWithSelectedText(true);
expect(spy).toHaveBeenCalled();
setTimeout(() => {
expect(spy).toHaveBeenCalled();
done();
});
});
});
});
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment