Skip to content

Help with Preserving Table Attributes and Styles on Paste in RTE #247

@yadprab

Description

@yadprab

Hi,

I am currently working on implementing the functionality to copy and paste tables into a rich text editor (RTE) while preserving the table's attributes, especially when users paste email signatures or content from Google Sheets. The goal is to maintain the original styles and attributes when the table is pasted.

However, I'm encountering an issue where styles from the pasted content are not preserved when the resizable option is set to true. This results in the original HTML being broken, and the styles from the pasted tables (like those from Sheets) are not maintained.

The following code demonstrates how I'm handling the CustomTableView and CustomTable extensions:

CustomTableView: Handles the parsing and insertion of the colgroup element.
CustomTable Extension: Manages table attributes and includes a conditional for enabling column resizing. The styles seem to break when resizing is enabled, but without it, the styles are preserved.

Since this issue is a go-live blocker for us, I would appreciate any help or guidance on how to preserve the styles while still supporting the resizable functionality.

export const CustomTable = Table.extend({
  addAttributes() {
    return {
      ...this.parent?.(),
      style: {
        default: 'border-collapse: collapse; margin: 0 !important; table-layout: fixed;',
        parseHTML: (element) => element.style?.cssText || '',
        renderHTML: (attributes) => ({ style: attributes.style }),
      },
      colgroup: {
        default: '',
        parseHTML: (element) => {
          const colgroup = element.querySelector('colgroup');

          return colgroup ? colgroup.outerHTML : null;
        },
        renderHTML: (attributes) => {
          return {
            colgroup: attributes.colgroup,
          };
        },
      },
      ispasted: {
        default: false,
        parseHTML: (element) => element.getAttribute('ispasted') === 'true',
      },
    };
  },

  addProseMirrorPlugins() {
    return [
  
      tableEditing({
        allowTableNodeSelection: this.options.allowTableNodeSelection,
      }),
      DefaultPastePlugin(),

      ...(this.options.resizable && this.options.HTMLAttributes.ispated !== true
        ? [
            columnResizing({
              handleWidth: this.options.handleWidth,
              cellMinWidth: this.options.cellMinWidth,
              View: CustomTableView,
              lastColumnResizable: this.options.lastColumnResizable,
            }),
          ]
        : []),
    ];
  },
});

import { Plugin } from 'prosemirror-state';

export function DefaultPastePlugin() {
  return new Plugin({
    props: {
      transformPastedHTML(html, view) {
        const dom = new DOMParser().parseFromString(html, 'text/html');
        const style = dom.querySelector('style');
        dom.body.querySelectorAll('*').forEach((element) => {
          element.setAttribute('isPasted', 'true');
        });
        const isFromExcel = Array.from(dom.querySelectorAll('meta')).some((meta) => {
          return (
            meta.getAttribute('name') === 'Generator' &&
            meta.getAttribute('content')?.includes('Excel')
          );
        });
        if (isFromExcel) {
          return dom.body.innerHTML;
        }

        if (style) {
          const cssRules = Array.from(style.sheet?.cssRules || []);
          cssRules.forEach((rule) => {
            if (rule instanceof CSSStyleRule) {
              const elements = dom.querySelectorAll(rule.selectorText);
              elements.forEach((element) => {
                if (element instanceof HTMLElement) {
                  // Merge existing styles with new styles
                  const existingStyles = element.getAttribute('style') || '';
                  element.setAttribute('style', `${existingStyles} ${rule.style.cssText}`);
                }
              });
            }
          });
          style.remove(); // Remove the <style> tag after applying the styles
        }
    

        return dom.body.innerHTML;
      },
    },
  });
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions