<commit_before>/*
 * table-nodes.ts
 *
 * Copyright (C) 2019-20 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

import { tableNodes } from 'prosemirror-tables';
import { Node as ProsemirrorNode, DOMOutputSpec } from 'prosemirror-model';

import { PandocTokenType } from '../../api/pandoc';

import {
  readPandocTable,
  writePandocTableContainer,
  writePandocTable,
  writePandocTableCaption,
  writePandocTableNodes,
  writePandocTableHeaderNodes,
} from './table-pandoc';
import { tableColumnWidths } from './table-columns';

const nodes = tableNodes({
  tableGroup: 'block',
  cellContent: 'block+',
  cellAttributes: {
    align: {
      default: null,
      getFromDOM(dom: Element) {
        return (dom as HTMLElement).style.textAlign || null;
      },
      setDOMAttr(value, attrs) {
        if (value) {
          attrs.style = (attrs.style || '') + `text-align: ${value};`;
        }
      },
    },
    className: {
      default: 'pm-table-cell pm-block-border-color',
      getFromDOM(dom: Element) {
        return (dom as HTMLElement).className;
      },
      setDOMAttr(value, attrs) {
        if (value) {
          attrs.class = value;
        }
      },
    },
  },
});

export const tableContainerNode = {
  name: 'table_container',
  spec: {
    content: 'table table_caption',
    group: 'block',
    parseDOM: [{ tag: "div[class*='table-container']" }],
    toDOM(node: ProsemirrorNode): DOMOutputSpec {
      return ['div', { class: 'table-container pm-table-container' }, 0];
    },
  },
  pandoc: {
    readers: [
      {
        token: PandocTokenType.Table,
        handler: readPandocTable,
      },
    ],
    writer: writePandocTableContainer,
  },
};

export const tableNode = {
  name: 'table',
  spec: {
    ...nodes.table,
    attrs: {
      ...nodes.table.attrs,
      width: { default: null },
      colpercents: { default: null },
    },
    parseDOM: [
      {
        tag: 'table',
        getAttrs: (dom: Node | string) => {
          const el = dom as HTMLElement;

          let width: number | null = null;
          let colpercents: number[] | null = null;

          const rows = el.getElementsByTagName('tr');
          if (rows.length) {
            const firstRow = rows.item(0)!;
            const numCells = firstRow.cells.length;
            const colWidths = new Array<number>(numCells).fill(0);
            for (let i = 0; i < numCells; i++) {
              const cell = firstRow.cells.item(i)!;
              const colWidth = cell.getAttribute('data-colwidth');
              if (colWidth) {
                colWidths[i] = Number(colWidth);
              }
            }
            if (colWidths.every(colWidth => colWidth > 0)) {
              width = colWidths.reduce((total, value) => total + value, 0);
              colpercents = colWidths.map(colWidth => colWidth / width!);
            }
          }

          return {
            width,
            colpercents,
          };
        },
      },
    ],
    toDOM(node: ProsemirrorNode): DOMOutputSpec {
      const attrs: any = {};
      if (node.attrs.width) {
        attrs['data-width'] = node.attrs.width.toString();
      }
      attrs['data-colpercents'] = tableColumnWidths(node).join(',');
      return ['table', attrs, ['tbody', 0]];
    },
  },
  pandoc: {
    writer: writePandocTable,
  },
};

export const tableCaptionNode = {
  name: 'table_caption',
  spec: {
    attrs: {
      inactive: { default: false },
    },
    content: 'inline*',
    parseDOM: [
      {
        tag: "p[class*='table-caption']",
        getAttrs(dom: Node | string) {
          return {
            inactive: (dom as HTMLElement).classList.contains('table-caption-inactive'),
          };
        },
      },
    ],
    toDOM(node: ProsemirrorNode): DOMOutputSpec {
      const classes = ['table-caption', 'pm-node-caption'];
      if (node.attrs.inactive) {
        classes.push('table-caption-inactive');
      }
      return ['p', { class: classes.join(' ') }, 0];
    },
  },
  pandoc: {
    writer: writePandocTableCaption,
  },
};

export const tableRowNode = {
  name: 'table_row',
  spec: nodes.table_row,
  pandoc: {
    writer: writePandocTableNodes,
  },
};

export const tableCellNode = (blocks: boolean) => ({
  name: 'table_cell',
  spec: {
    ...nodes.table_cell,
    content: blocks ? 'block+' : 'block',
  },
  pandoc: {
    writer: writePandocTableNodes,
  },
});

export const tableHeaderNode = {
  name: 'table_header',
  spec: nodes.table_header,
  pandoc: {
    writer: writePandocTableHeaderNodes,
  },
};
<commit_msg>ensure that pasted tables have borders<commit_after>/*
 * table-nodes.ts
 *
 * Copyright (C) 2019-20 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

import { tableNodes } from 'prosemirror-tables';
import { Node as ProsemirrorNode, DOMOutputSpec } from 'prosemirror-model';

import { PandocTokenType } from '../../api/pandoc';

import {
  readPandocTable,
  writePandocTableContainer,
  writePandocTable,
  writePandocTableCaption,
  writePandocTableNodes,
  writePandocTableHeaderNodes,
} from './table-pandoc';
import { tableColumnWidths } from './table-columns';

const kDefaultCellClasses = 'pm-table-cell pm-block-border-color';

const nodes = tableNodes({
  tableGroup: 'block',
  cellContent: 'block+',
  cellAttributes: {
    align: {
      default: null,
      getFromDOM(dom: Element) {
        return (dom as HTMLElement).style.textAlign || null;
      },
      setDOMAttr(value, attrs) {
        if (value) {
          attrs.style = (attrs.style || '') + `text-align: ${value};`;
        }
      },
    },
    className: {
      default: kDefaultCellClasses,
      getFromDOM(dom: Element) {
        return (dom as HTMLElement).className;
      },
      setDOMAttr(_value, attrs) {
        attrs.class = kDefaultCellClasses;
      },
    },
  },
});

export const tableContainerNode = {
  name: 'table_container',
  spec: {
    content: 'table table_caption',
    group: 'block',
    parseDOM: [{ tag: "div[class*='table-container']" }],
    toDOM(node: ProsemirrorNode): DOMOutputSpec {
      return ['div', { class: 'table-container pm-table-container' }, 0];
    },
  },
  pandoc: {
    readers: [
      {
        token: PandocTokenType.Table,
        handler: readPandocTable,
      },
    ],
    writer: writePandocTableContainer,
  },
};

export const tableNode = {
  name: 'table',
  spec: {
    ...nodes.table,
    attrs: {
      ...nodes.table.attrs,
      width: { default: null },
      colpercents: { default: null },
    },
    parseDOM: [
      {
        tag: 'table',
        getAttrs: (dom: Node | string) => {
          const el = dom as HTMLElement;

          let width: number | null = null;
          let colpercents: number[] | null = null;

          const rows = el.getElementsByTagName('tr');
          if (rows.length) {
            const firstRow = rows.item(0)!;
            const numCells = firstRow.cells.length;
            const colWidths = new Array<number>(numCells).fill(0);
            for (let i = 0; i < numCells; i++) {
              const cell = firstRow.cells.item(i)!;
              const colWidth = cell.getAttribute('data-colwidth');
              if (colWidth) {
                colWidths[i] = Number(colWidth);
              }
            }
            if (colWidths.every(colWidth => colWidth > 0)) {
              width = colWidths.reduce((total, value) => total + value, 0);
              colpercents = colWidths.map(colWidth => colWidth / width!);
            }
          }

          return {
            width,
            colpercents,
          };
        },
      },
    ],
    toDOM(node: ProsemirrorNode): DOMOutputSpec {
      const attrs: any = {};
      if (node.attrs.width) {
        attrs['data-width'] = node.attrs.width.toString();
      }
      attrs['data-colpercents'] = tableColumnWidths(node).join(',');
      return ['table', attrs, ['tbody', 0]];
    },
  },
  pandoc: {
    writer: writePandocTable,
  },
};

export const tableCaptionNode = {
  name: 'table_caption',
  spec: {
    attrs: {
      inactive: { default: false },
    },
    content: 'inline*',
    parseDOM: [
      {
        tag: "p[class*='table-caption']",
        getAttrs(dom: Node | string) {
          return {
            inactive: (dom as HTMLElement).classList.contains('table-caption-inactive'),
          };
        },
      },
    ],
    toDOM(node: ProsemirrorNode): DOMOutputSpec {
      const classes = ['table-caption', 'pm-node-caption'];
      if (node.attrs.inactive) {
        classes.push('table-caption-inactive');
      }
      return ['p', { class: classes.join(' ') }, 0];
    },
  },
  pandoc: {
    writer: writePandocTableCaption,
  },
};

export const tableRowNode = {
  name: 'table_row',
  spec: nodes.table_row,
  pandoc: {
    writer: writePandocTableNodes,
  },
};

export const tableCellNode = (blocks: boolean) => ({
  name: 'table_cell',
  spec: {
    ...nodes.table_cell,
    content: blocks ? 'block+' : 'block',
  },
  pandoc: {
    writer: writePandocTableNodes,
  },
});

export const tableHeaderNode = {
  name: 'table_header',
  spec: nodes.table_header,
  pandoc: {
    writer: writePandocTableHeaderNodes,
  },
};

{'fraction_non_alphanumeric': 0.13765182186234817, 'fraction_numerical': 0.0033573615088377602, 'mean_word_length': 2.3436777814460217, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '372033', 'n_tokens_mistral': 3235, 'n_tokens_neox': 3190, 'n_words': 730}