ProseMirror / prosemirror-tables

Table module for ProseMirror
https://prosemirror-tables.netlify.app/
MIT License
259 stars 155 forks source link

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

Open yadprab opened 1 month ago

yadprab commented 1 month ago

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;
      },
    },
  });
}