Fix support for tables pasted from Excel which contain a <style> tag in their HTML #1773

This commit is contained in:
Ben Gotow 2020-02-16 19:25:04 -06:00
parent 5c912578c4
commit 378bc9415f
5 changed files with 59 additions and 22 deletions

View file

@ -576,7 +576,11 @@ export default class Application extends EventEmitter {
out = html;
}
// win = BrowserWindow.fromWebContents(event.sender)
event.sender.send('inline-styles-result', { html: out, key });
if (key) {
event.sender.send('inline-styles-result', { html: out, key });
} else {
event.returnValue = out;
}
});
app.on('activate', (event, hasVisibleWindows) => {

View file

@ -1,8 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import * as Immutable from 'immutable';
import { Editor, Value, Operation, Range } from 'slate';
import { Editor as SlateEditorComponent, EditorProps } from 'slate-react';
import { clipboard as ElectronClipboard } from 'electron';
import { InlineStyleTransformer } from 'mailspring-exports';
import path from 'path';
import fs from 'fs';
@ -11,7 +13,6 @@ import ComposerEditorToolbar from './composer-editor-toolbar';
import { schema, plugins, convertFromHTML, convertToHTML, convertToPlainText } from './conversion';
import { lastUnquotedNode, removeQuotedText } from './base-block-plugins';
import { changes as InlineAttachmentChanges } from './inline-attachment-plugins';
import ReactDOM from 'react-dom';
const AEditor = (SlateEditorComponent as any) as React.ComponentType<
EditorProps & { ref: any; propsForPlugins: any }
@ -211,9 +212,17 @@ export class ComposerEditor extends React.Component<ComposerEditorProps> {
}
}
// handle text/html paste
const html = event.clipboardData.getData('text/html');
let html = event.clipboardData.getData('text/html');
if (html) {
// Unfortuantely, pasting HTML requires an synchronous hop through our main process style
// transfomer. This ensures that we inline styles and preserve as much as possible.
// (eg: pasting tables from Excel).
try {
html = InlineStyleTransformer.runSync(html);
} catch (err) {
//no-op
}
const value = convertFromHTML(html);
if (value && value.document) {
editor.insertFragment(value.document);

View file

@ -27,6 +27,11 @@ import { Rule, ComposerEditorPlugin } from './types';
import './patch-chrome-ime';
export const schema = {
blocks: {
[UNEDITABLE_TYPE]: {
isVoid: true,
},
},
inlines: {
[VARIABLE_TYPE]: {
isVoid: true,

View file

@ -7,18 +7,15 @@ import RegExpUtils from '../regexp-utils';
let userAgentDefault = null;
class InlineStyleTransformer {
_inlineStylePromises = {};
_inlineStyleResolvers = {};
_inlineStylePromises: { [key: string]: Promise<string> } = {};
_inlineStyleResolvers: { [key: string]: (result: string) => void } = {};
constructor() {
ipcRenderer.on('inline-styles-result', this._onInlineStylesResult);
}
run = html => {
if (!html || typeof html !== 'string' || html.length <= 0) {
return Promise.resolve(html);
}
if (!RegExpUtils.looseStyleTag().test(html)) {
run = (html: string) => {
if (!this._requiresProcessing(html)) {
return Promise.resolve(html);
}
@ -27,24 +24,46 @@ class InlineStyleTransformer {
.update(html)
.digest('hex');
// http://stackoverflow.com/questions/8695031/why-is-there-often-a-inside-the-style-tag
// https://regex101.com/r/bZ5tX4/1
let styled = html.replace(
/<style[^>]*>[\n\r \t]*<!--([^</]*)-->[\n\r \t]*<\/style/g,
(full, content) => `<style>${content}</style`
);
styled = this._injectUserAgentStyles(styled);
if (this._inlineStylePromises[key] == null) {
html = this._prepareHTMLForInlineStyling(html);
this._inlineStylePromises[key] = new Promise(resolve => {
this._inlineStyleResolvers[key] = resolve;
ipcRenderer.send('inline-style-parse', { html: styled, key: key });
ipcRenderer.send('inline-style-parse', { html, key });
});
}
return this._inlineStylePromises[key];
};
runSync = (html: string) => {
if (!this._requiresProcessing(html)) return html;
html = this._prepareHTMLForInlineStyling(html);
return ipcRenderer.sendSync('inline-style-parse', { html, key: '' });
};
_requiresProcessing(html: string) {
if (!html || typeof html !== 'string' || html.length <= 0) {
return false;
}
if (!RegExpUtils.looseStyleTag().test(html)) {
return false;
}
return true;
}
_prepareHTMLForInlineStyling(html: string) {
// http://stackoverflow.com/questions/8695031/why-is-there-often-a-inside-the-style-tag
// https://regex101.com/r/bZ5tX4/1
let result = html.replace(
/<style[^>]*>[\n\r \t]*<!--([^</]*)-->[\n\r \t]*<\/style/g,
(full, content) => `<style>${content}</style`
);
result = this._injectUserAgentStyles(result);
return result;
}
// This will prepend the user agent stylesheet so we can apply it to the
// styles properly.
_injectUserAgentStyles(body) {

@ -1 +1 @@
Subproject commit 7f7642b3a642761ce0ea5c5102a969afc059a2ea
Subproject commit e44852a479a3f9013e6fd51b3d36c27126c142e5