<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import omit from 'lodash.omit';
import {
  IconChevronUpSmallOutline,
  IconChevronDownSmallOutline,
  IconWarningBadgedSmallFilled,
} from '@teamleader/ui-icons';
import Box, { omitBoxProps, pickBoxProps } from '../box';
import Button from '../button';
import { TextSmall } from '../typography';
import theme from './theme.css';

export default class Input extends PureComponent {
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.value !== undefined) {
      const newValue = nextProps.value || '';

      if (newValue !== prevState.value) {
        return {
          value: newValue,
        };
      }
    }

    return null;
  }

  parseValue(value) {
    const { type } = this.props;
    return type === 'number' ? this.bindToMinMax(this.toNumber(value)) : value;
  }

  toNumber(number) {
    let float = parseFloat(number);

    if (isNaN(float) || !isFinite(float)) {
      float = 0;
    }

    return float;
  }

  bindToMinMax(value) {
    const { min, max } = this.props;
    return Math.min(Math.max(value, min), max);
  }

  state = {
    value: '',
  };

  handleBlur = event => {
    const { value } = this.state;
    const parsedValue = this.parseValue(value);

    if (parsedValue !== value) {
      this.updateValue(event, parsedValue);
    }

    if (this.props.onBlur) {
      this.props.onBlur();
    }
  };

  handleChange = event => {
    this.updateValue(event, event.target.value);
  };

  handleIncreaseValue = event => {
    this.updateStep(event, 1);
  };

  handleDecreaseValue = event => {
    this.updateStep(event, -1);
  };

  updateValue(event, rawValue, triggerOnChange = true) {
    const { onChange } = this.props;
    const newValue = String(rawValue);

    this.setState({
      value: newValue,
    });

    if (triggerOnChange && onChange) {
      onChange(event, newValue);
    }
  }

  updateStep(event, n) {
    const { step } = this.props;

    const currentValue = this.toNumber(this.state.value || 0);
    const newValue = this.parseValue(currentValue + step * n);

    if (newValue !== currentValue) {
      this.updateValue(event, newValue);
    }
  }

  renderInput() {
    const { bold, max, min, step, type, ...otherProps } = this.props;
    const { value } = this.state;

    const classNames = cx(theme['input'], {
      [theme['is-bold']]: bold,
    });

    const otherPropsWithoutBoxProps = omitBoxProps(otherProps);
    const restProps = omit(otherPropsWithoutBoxProps, [
      'className',
      'connectedLeft',
      'connectedRight',
      'helpText',
      'inverse',
      'onChange',
      'size',
      'spinner',
      'value',
    ]);

    const numberTypeProps = {
      max,
      min,
      step,
      value,
    };

    const props = {
      className: classNames,
      onBlur: this.handleBlur,
      onChange: this.handleChange,
      type,
      value,
      ...restProps,
      ...(type === 'number' && numberTypeProps),
    };

    return <input {...props} />;
  }

  renderHelpText() {
    const { helpText, inverse } = this.props;

    if (helpText) {
      return (
        <TextSmall color={inverse ? 'white' : 'neutral'} marginTop={1} soft>
          {helpText}
        </TextSmall>
      );
    }
  }

  renderSpinnerControls() {
    const { disabled, readOnly, spinner, type } = this.props;

    const props = {
      disabled: disabled || readOnly,
    };

    if (type === 'number' && spinner) {
      return (

          <Button
            className={theme['spinner-up']}
            icon={<IconChevronUpSmallOutline />}
            onClick={this.handleIncreaseValue}
            {...props}
          />
          <Button
            className={theme['spinner-down']}
            icon={<IconChevronDownSmallOutline />}
            onClick={this.handleDecreaseValue}
            {...props}
          />

      );
    }
  }

  renderValidationMessage() {
    return (
      <TextSmall className={theme['validation-text']} marginTop={2} display="flex">
        <Box element="span" className={theme['validation-icon']}>
          <IconWarningBadgedSmallFilled />
        </Box>
        <Box element="span" marginLeft={1}>
          {this.props.error}
        </Box>
      </TextSmall>
    );
  }

  render() {
    const {
      className,
      connectedLeft,
      connectedRight,
      disabled,
      error,
      inverse,
      size,
      spinner,
      readOnly,
      type,
      ...others
    } = this.props;

    const classNames = cx(
      theme['wrapper'],
      theme[`is-${size}`],
      {
        [theme['has-error']]: error,
        [theme['has-connected-left']]: connectedLeft,
        [theme['has-connected-right']]: connectedRight,
        [theme['has-spinner']]: type === 'number' && spinner,
        [theme['is-inverse']]: inverse,
        [theme['is-disabled']]: disabled,
        [theme['is-read-only']]: readOnly,
      },
      className,
    );

    const inputWrapperClassnames = cx(theme['input-wrapper'], {
      [theme['has-error']]: error,
    });

    const rest = pickBoxProps(others);

    return (
      <Box className={classNames} {...rest}>

          {connectedLeft}

            {this.renderInput()}
            {this.renderSpinnerControls()}

          {connectedRight}

        {error ? this.renderValidationMessage() : this.renderHelpText()}
      </Box>
    );
  }
}

Input.propTypes = {
  /** Boolean indicating whether the input text should render in bold. */
  bold: PropTypes.bool,
  /** Sets a class name for the wrapper to give custom styles. */
  className: PropTypes.string,
  connectedLeft: PropTypes.element,
  connectedRight: PropTypes.element,
  /** Boolean indicating whether the input should render as disabled. */
  disabled: PropTypes.bool,
  /** The text string/element to use as error message below the input. */
  error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /** The text string to use as help text below the input. */
  helpText: PropTypes.string,
  /** Boolean indicating whether the input should render as inverse. */
  inverse: PropTypes.bool,
  max: PropTypes.number,
  min: PropTypes.number,
  /** Boolean indicating whether to number type input should render spinner controls */
  spinner: PropTypes.bool,
  /** Callback function that is fired when blurring the input field. */
  onBlur: PropTypes.func,
  /** Callback function that is fired when the component's value changes. */
  onChange: PropTypes.func,
  /** The text string/element to use as a prefix inside the input field */
  prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /** Boolean indicating whether the input should render as read only. */
  readOnly: PropTypes.bool,
  /** Size of the input element. */
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  /** Limit increment value for numeric inputs. */
  step: PropTypes.number,
  /** The text string/element to use as a suffix inside the input field */
  suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /** Type of the input element. It can be a valid HTML5 input type. */
  type: PropTypes.string,
  /** Current value of the input element. */
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

Input.defaultProps = {
  inverse: false,
  disabled: false,
  min: Number.MIN_SAFE_INTEGER,
  max: Number.MAX_SAFE_INTEGER,
  readOnly: false,
  size: 'medium',
  spinner: true,
  step: 1,
};
<commit_msg>Render prefix & suffix in place<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import omit from 'lodash.omit';
import {
  IconChevronUpSmallOutline,
  IconChevronDownSmallOutline,
  IconWarningBadgedSmallFilled,
} from '@teamleader/ui-icons';
import Box, { omitBoxProps, pickBoxProps } from '../box';
import Button from '../button';
import { TextSmall } from '../typography';
import theme from './theme.css';

export default class Input extends PureComponent {
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.value !== undefined) {
      const newValue = nextProps.value || '';

      if (newValue !== prevState.value) {
        return {
          value: newValue,
        };
      }
    }

    return null;
  }

  parseValue(value) {
    const { type } = this.props;
    return type === 'number' ? this.bindToMinMax(this.toNumber(value)) : value;
  }

  toNumber(number) {
    let float = parseFloat(number);

    if (isNaN(float) || !isFinite(float)) {
      float = 0;
    }

    return float;
  }

  bindToMinMax(value) {
    const { min, max } = this.props;
    return Math.min(Math.max(value, min), max);
  }

  state = {
    value: '',
  };

  handleBlur = event => {
    const { value } = this.state;
    const parsedValue = this.parseValue(value);

    if (parsedValue !== value) {
      this.updateValue(event, parsedValue);
    }

    if (this.props.onBlur) {
      this.props.onBlur();
    }
  };

  handleChange = event => {
    this.updateValue(event, event.target.value);
  };

  handleIncreaseValue = event => {
    this.updateStep(event, 1);
  };

  handleDecreaseValue = event => {
    this.updateStep(event, -1);
  };

  updateValue(event, rawValue, triggerOnChange = true) {
    const { onChange } = this.props;
    const newValue = String(rawValue);

    this.setState({
      value: newValue,
    });

    if (triggerOnChange && onChange) {
      onChange(event, newValue);
    }
  }

  updateStep(event, n) {
    const { step } = this.props;

    const currentValue = this.toNumber(this.state.value || 0);
    const newValue = this.parseValue(currentValue + step * n);

    if (newValue !== currentValue) {
      this.updateValue(event, newValue);
    }
  }

  renderInput() {
    const { bold, max, min, step, type, ...otherProps } = this.props;
    const { value } = this.state;

    const classNames = cx(theme['input'], {
      [theme['is-bold']]: bold,
    });

    const otherPropsWithoutBoxProps = omitBoxProps(otherProps);
    const restProps = omit(otherPropsWithoutBoxProps, [
      'className',
      'connectedLeft',
      'connectedRight',
      'helpText',
      'inverse',
      'onChange',
      'size',
      'spinner',
      'value',
    ]);

    const numberTypeProps = {
      max,
      min,
      step,
      value,
    };

    const props = {
      className: classNames,
      onBlur: this.handleBlur,
      onChange: this.handleChange,
      type,
      value,
      ...restProps,
      ...(type === 'number' && numberTypeProps),
    };

    return <input {...props} />;
  }

  renderHelpText() {
    const { helpText, inverse } = this.props;

    if (helpText) {
      return (
        <TextSmall color={inverse ? 'white' : 'neutral'} marginTop={1} soft>
          {helpText}
        </TextSmall>
      );
    }
  }

  renderSpinnerControls() {
    const { disabled, readOnly, spinner, type } = this.props;

    const props = {
      disabled: disabled || readOnly,
    };

    if (type === 'number' && spinner) {
      return (

          <Button
            className={theme['spinner-up']}
            icon={<IconChevronUpSmallOutline />}
            onClick={this.handleIncreaseValue}
            {...props}
          />
          <Button
            className={theme['spinner-down']}
            icon={<IconChevronDownSmallOutline />}
            onClick={this.handleDecreaseValue}
            {...props}
          />

      );
    }
  }

  renderValidationMessage() {
    return (
      <TextSmall className={theme['validation-text']} marginTop={2} display="flex">
        <Box element="span" className={theme['validation-icon']}>
          <IconWarningBadgedSmallFilled />
        </Box>
        <Box element="span" marginLeft={1}>
          {this.props.error}
        </Box>
      </TextSmall>
    );
  }

  render() {
    const {
      className,
      connectedLeft,
      connectedRight,
      disabled,
      error,
      inverse,
      prefix,
      size,
      spinner,
      suffix,
      readOnly,
      type,
      ...others
    } = this.props;

    const classNames = cx(
      theme['wrapper'],
      theme[`is-${size}`],
      {
        [theme['has-error']]: error,
        [theme['has-connected-left']]: connectedLeft,
        [theme['has-connected-right']]: connectedRight,
        [theme['has-spinner']]: type === 'number' && spinner,
        [theme['is-inverse']]: inverse,
        [theme['is-disabled']]: disabled,
        [theme['is-read-only']]: readOnly,
      },
      className,
    );

    const inputWrapperClassnames = cx(theme['input-wrapper'], {
      [theme['has-error']]: error,
    });

    const rest = pickBoxProps(others);

    return (
      <Box className={classNames} {...rest}>

          {connectedLeft}

            {prefix}
            {this.renderInput()}
            {suffix}
            {this.renderSpinnerControls()}

          {connectedRight}

        {error ? this.renderValidationMessage() : this.renderHelpText()}
      </Box>
    );
  }
}

Input.propTypes = {
  /** Boolean indicating whether the input text should render in bold. */
  bold: PropTypes.bool,
  /** Sets a class name for the wrapper to give custom styles. */
  className: PropTypes.string,
  connectedLeft: PropTypes.element,
  connectedRight: PropTypes.element,
  /** Boolean indicating whether the input should render as disabled. */
  disabled: PropTypes.bool,
  /** The text string/element to use as error message below the input. */
  error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /** The text string to use as help text below the input. */
  helpText: PropTypes.string,
  /** Boolean indicating whether the input should render as inverse. */
  inverse: PropTypes.bool,
  max: PropTypes.number,
  min: PropTypes.number,
  /** Boolean indicating whether to number type input should render spinner controls */
  spinner: PropTypes.bool,
  /** Callback function that is fired when blurring the input field. */
  onBlur: PropTypes.func,
  /** Callback function that is fired when the component's value changes. */
  onChange: PropTypes.func,
  /** The text string/element to use as a prefix inside the input field */
  prefix: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /** Boolean indicating whether the input should render as read only. */
  readOnly: PropTypes.bool,
  /** Size of the input element. */
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  /** Limit increment value for numeric inputs. */
  step: PropTypes.number,
  /** The text string/element to use as a suffix inside the input field */
  suffix: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /** Type of the input element. It can be a valid HTML5 input type. */
  type: PropTypes.string,
  /** Current value of the input element. */
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

Input.defaultProps = {
  inverse: false,
  disabled: false,
  min: Number.MIN_SAFE_INTEGER,
  max: Number.MAX_SAFE_INTEGER,
  readOnly: false,
  size: 'medium',
  spinner: true,
  step: 1,
};
<|endoftext|>


<commit_before>/**
 *
 * ListView
 *
 */

import React, { useEffect, useReducer, useRef, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { useIntl } from 'react-intl';
import {
  request,
  useRBAC,
  LoadingIndicatorPage,
  useNotification,
  useFocusWhenNavigate,
  SettingsPageTitle,
  ConfirmDialog,
  onRowClick,
  stopPropagation,
} from '@strapi/helper-plugin';
import { HeaderLayout, Layout, ContentLayout, ActionLayout } from '@strapi/design-system/Layout';
import { EmptyStateLayout } from '@strapi/design-system/EmptyStateLayout';
import { Flex } from '@strapi/design-system/Flex';
import { Stack } from '@strapi/design-system/Stack';
import { IconButton } from '@strapi/design-system/IconButton';
import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
import { Table, Thead, Tr, Th, Tbody, Td, TFooter } from '@strapi/design-system/Table';
import { Text, TableLabel, Subtitle } from '@strapi/design-system/Text';
import { Button } from '@strapi/design-system/Button';
import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
import { Switch } from '@strapi/design-system/Switch';
import { Main } from '@strapi/design-system/Main';
import { LinkButton } from '@strapi/design-system/LinkButton';
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
import { Box } from '@strapi/design-system/Box';
import Plus from '@strapi/icons/Plus';
import Pencil from '@strapi/icons/Pencil';
import Trash from '@strapi/icons/Trash';
import EmptyDocuments from '@strapi/icons/EmptyDocuments';
import reducer, { initialState } from './reducer';
import adminPermissions from '../../../../../permissions';

const ListView = () => {
  const {
    isLoading,
    allowedActions: { canCreate, canRead, canUpdate, canDelete },
  } = useRBAC(adminPermissions.settings.webhooks);
  const toggleNotification = useNotification();
  const isMounted = useRef(true);
  const { formatMessage } = useIntl();
  const [showModal, setShowModal] = useState(false);
  const [{ webhooks, webhooksToDelete, webhookToDelete, loadingWebhooks }, dispatch] = useReducer(
    reducer,
    initialState
  );
  const { notifyStatus } = useNotifyAT();

  useFocusWhenNavigate();
  const { push } = useHistory();
  const { pathname } = useLocation();
  const rowsCount = webhooks.length;
  const webhooksToDeleteLength = webhooksToDelete.length;
  const getWebhookIndex = id => webhooks.findIndex(webhook => webhook.id === id);

  useEffect(() => {
    isMounted.current = true;

    return () => {
      isMounted.current = false;
    };
  }, []);

  useEffect(() => {
    if (canRead) {
      fetchWebHooks();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [canRead]);

  const fetchWebHooks = async () => {
    try {
      const { data } = await request('/admin/webhooks', {
        method: 'GET',
      });

      if (isMounted.current) {
        dispatch({
          type: 'GET_DATA_SUCCEEDED',
          data,
        });
        notifyStatus('webhooks have been loaded');
      }
    } catch (err) {
      console.log(err);

      if (isMounted.current) {
        if (err.code !== 20) {
          toggleNotification({
            type: 'warning',
            message: { id: 'notification.error' },
          });
        }
        dispatch({
          type: 'TOGGLE_LOADING',
        });
      }
    }
  };

  const handleToggleModal = () => {
    setShowModal(prev => !prev);
  };

  const handleConfirmDelete = () => {
    if (webhookToDelete) {
      handleConfirmDeleteOne();
    } else {
      handleConfirmDeleteAll();
    }
  };

  const handleConfirmDeleteOne = async () => {
    try {
      await request(`/admin/webhooks/${webhookToDelete}`, {
        method: 'DELETE',
      });

      dispatch({
        type: 'WEBHOOK_DELETED',
        index: getWebhookIndex(webhookToDelete),
      });
    } catch (err) {
      if (err.code !== 20) {
        toggleNotification({
          type: 'warning',
          message: { id: 'notification.error' },
        });
      }
    }
    setShowModal(false);
  };

  const handleConfirmDeleteAll = async () => {
    const body = {
      ids: webhooksToDelete,
    };

    try {
      await request('/admin/webhooks/batch-delete', {
        method: 'POST',
        body,
      });

      if (isMounted.current) {
        dispatch({
          type: 'WEBHOOKS_DELETED',
        });
      }
    } catch (err) {
      if (isMounted.current) {
        if (err.code !== 20) {
          toggleNotification({
            type: 'warning',
            message: { id: 'notification.error' },
          });
        }
      }
    }
    setShowModal(false);
  };

  const handleDeleteClick = id => {
    setShowModal(true);

    if (id !== 'all') {
      dispatch({
        type: 'SET_WEBHOOK_TO_DELETE',
        id,
      });
    }
  };

  const handleEnabledChange = async (value, id) => {
    const webhookIndex = getWebhookIndex(id);
    const initialWebhookProps = webhooks[webhookIndex];
    const keys = [webhookIndex, 'isEnabled'];

    const body = {
      ...initialWebhookProps,
      isEnabled: value,
    };

    delete body.id;

    try {
      dispatch({
        type: 'SET_WEBHOOK_ENABLED',
        keys,
        value,
      });

      await request(`/admin/webhooks/${id}`, {
        method: 'PUT',
        body,
      });
    } catch (err) {
      if (isMounted.current) {
        dispatch({
          type: 'SET_WEBHOOK_ENABLED',
          keys,
          value: !value,
        });

        if (err.code !== 20) {
          toggleNotification({
            type: 'warning',
            message: { id: 'notification.error' },
          });
        }
      }
    }
  };

  const handleSelectAllCheckbox = () => {
    dispatch({
      type: 'SET_ALL_WEBHOOKS_TO_DELETE',
    });
  };

  const handleSelectOneCheckbox = (value, id) => {
    dispatch({
      type: 'SET_WEBHOOKS_TO_DELETE',
      value,
      id,
    });
  };

  const handleGoTo = to => {
    push(`${pathname}/${to}`);
  };

  return (
    <Layout>
      <SettingsPageTitle name="Webhooks" />

        <>
          <HeaderLayout
            title={formatMessage({ id: 'Settings.webhooks.title', defaultMessage: 'Webhooks' })}
            subtitle={formatMessage({
              id: 'Settings.webhooks.list.description',
              defaultMessage: 'Get POST changes notifications.',
            })}
            primaryAction={
              canCreate &&
              !loadingWebhooks && (
                <LinkButton
                  startIcon={<Plus />}
                  variant="default"
                  to={`${pathname}/create`}
                  size="L"
                >
                  {formatMessage({
                    id: 'Settings.webhooks.list.button.add',
                    defaultMessage: 'Add new webhook',
                  })}
                </LinkButton>
              )
            }
          />
          {webhooksToDeleteLength > 0 && canDelete && (
            <ActionLayout
              startActions={
                <>
                  <Subtitle textColor="neutral600">
                    {formatMessage(
                      {
                        id: 'Settings.webhooks.to.delete',
                        defaultMessage:
                          '{webhooksToDeleteLength, plural, one {# asset} other {# assets}} selected',
                      },
                      { webhooksToDeleteLength }
                    )}
                  </Subtitle>
                  <Button
                    onClick={() => handleDeleteClick('all')}
                    startIcon={<Trash />}
                    size="L"
                    variant="danger-light"
                  >
                    Delete
                  </Button>
                </>
              }
            />
          )}
          <ContentLayout>
            {isLoading || loadingWebhooks ? (
              <Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
                <LoadingIndicatorPage />
              </Box>
            ) : (
              <>
                {rowsCount > 0 ? (
                  <Table
                    colCount={5}
                    rowCount={rowsCount + 1}
                    footer={
                      <TFooter
                        onClick={() => (canCreate ? handleGoTo('create') : {})}
                        icon={<Plus />}
                      >
                        {formatMessage({
                          id: 'Settings.webhooks.list.button.add',
                          defaultMessage: 'Add new webhook',
                        })}
                      </TFooter>
                    }
                  >

                          <BaseCheckbox
                            aria-label={formatMessage({
                              id: 'Settings.webhooks.list.all-entries.select',
                              defaultMessage: 'Select all entries',
                            })}
                            indeterminate={
                              webhooksToDeleteLength > 0 && webhooksToDeleteLength < rowsCount
                            }
                            value={webhooksToDeleteLength === rowsCount}
                            onValueChange={handleSelectAllCheckbox}
                          />

                          <TableLabel textColor="neutral600">
                            {formatMessage({
                              id: 'Settings.webhooks.form.name',
                              defaultMessage: 'Name',
                            })}
                          </TableLabel>

                          <TableLabel textColor="neutral600">
                            {formatMessage({
                              id: 'Settings.webhooks.form.url',
                              defaultMessage: 'URL',
                            })}
                          </TableLabel>

                          <TableLabel textColor="neutral600">
                            {formatMessage({
                              id: 'Settings.webhooks.list.th.status',
                              defaultMessage: 'Status',
                            })}
                          </TableLabel>

                          <VisuallyHidden>
                            {formatMessage({
                              id: 'Settings.webhooks.list.th.actions',
                              defaultMessage: 'Actions',
                            })}
                          </VisuallyHidden>

                      {webhooks.map(webhook => (

 handleGoTo(webhook.id),
                            condition: canUpdate,
                          })}
                        >

                            <BaseCheckbox
                              aria-label={`${formatMessage({
                                id: 'Settings.webhooks.list.select',
                                defaultMessage: 'Select',
                              })} ${webhook.name}`}
                              value={webhooksToDelete?.includes(webhook.id)}
                              onValueChange={value => handleSelectOneCheckbox(value, webhook.id)}
                              id="select"
                              name="select"
                            />

                            <Text bold textColor="neutral800">
                              {webhook.name}
                            </Text>

                            <Text textColor="neutral800">{webhook.url}</Text>

                            <Flex {...stopPropagation}>
                              <Switch
                                onLabel={formatMessage({
                                  id: 'Settings.webhooks.enabled',
                                  defaultMessage: 'Enabled',
                                })}
                                offLabel={formatMessage({
                                  id: 'Settings.webhooks.disabled',
                                  defaultMessage: 'Disabled',
                                })}
                                label={`${webhook.name} ${formatMessage({
                                  id: 'Settings.webhooks.list.th.status',
                                  defaultMessage: 'Status',
                                })}`}
                                selected={webhook.isEnabled}
                                onChange={() => handleEnabledChange(!webhook.isEnabled, webhook.id)}
                                visibleLabels
                              />
                            </Flex>

                            <Stack horizontal size={1} {...stopPropagation}>
                              {canUpdate && (
                                <IconButton
                                  onClick={() => {
                                    handleGoTo(webhook.id);
                                  }}
                                  label={formatMessage({
                                    id: 'Settings.webhooks.events.update',
                                    defaultMessage: 'Update',
                                  })}
                                  icon={<Pencil />}
                                  noBorder
                                />
                              )}
                              {canDelete && (
                                <IconButton
                                  onClick={() => handleDeleteClick(webhook.id)}
                                  label={formatMessage({
                                    id: 'Settings.webhooks.events.delete',
                                    defaultMessage: 'Delete',
                                  })}
                                  icon={<Trash />}
                                  noBorder
                                  id={`delete-${webhook.id}`}
                                />
                              )}
                            </Stack>

                      ))}

                ) : (
                  <EmptyStateLayout
                    icon={<EmptyDocuments width="160px" />}
                    content={formatMessage({
                      id: 'Settings.webhooks.list.empty.description',
                      defaultMessage: 'Add your first webhook',
                    })}
                    action={
                      <Button
                        variant="secondary"
                        startIcon={<Plus />}
                        onClick={() => (canCreate ? handleGoTo('create') : {})}
                      >
                        {formatMessage({
                          id: 'Settings.webhooks.list.button.add',
                          defaultMessage: 'Add new webhook',
                        })}
                      </Button>
                    }
                  />
                )}
              </>
            )}
          </ContentLayout>
        </>

      <ConfirmDialog
        isOpen={showModal}
        onToggleDialog={handleToggleModal}
        onConfirm={handleConfirmDelete}
      />
    </Layout>
  );
};

export default ListView;
<commit_msg>typography in LV<commit_after>/**
 *
 * ListView
 *
 */

import React, { useEffect, useReducer, useRef, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom';
import { useIntl } from 'react-intl';
import {
  request,
  useRBAC,
  LoadingIndicatorPage,
  useNotification,
  useFocusWhenNavigate,
  SettingsPageTitle,
  ConfirmDialog,
  onRowClick,
  stopPropagation,
} from '@strapi/helper-plugin';
import { HeaderLayout, Layout, ContentLayout, ActionLayout } from '@strapi/design-system/Layout';
import { EmptyStateLayout } from '@strapi/design-system/EmptyStateLayout';
import { Flex } from '@strapi/design-system/Flex';
import { Stack } from '@strapi/design-system/Stack';
import { IconButton } from '@strapi/design-system/IconButton';
import { BaseCheckbox } from '@strapi/design-system/BaseCheckbox';
import { Table, Thead, Tr, Th, Tbody, Td, TFooter } from '@strapi/design-system/Table';
import { Typography } from '@strapi/design-system/Typography';
import { Button } from '@strapi/design-system/Button';
import { VisuallyHidden } from '@strapi/design-system/VisuallyHidden';
import { Switch } from '@strapi/design-system/Switch';
import { Main } from '@strapi/design-system/Main';
import { LinkButton } from '@strapi/design-system/LinkButton';
import { useNotifyAT } from '@strapi/design-system/LiveRegions';
import { Box } from '@strapi/design-system/Box';
import Plus from '@strapi/icons/Plus';
import Pencil from '@strapi/icons/Pencil';
import Trash from '@strapi/icons/Trash';
import EmptyDocuments from '@strapi/icons/EmptyDocuments';
import reducer, { initialState } from './reducer';
import adminPermissions from '../../../../../permissions';

const ListView = () => {
  const {
    isLoading,
    allowedActions: { canCreate, canRead, canUpdate, canDelete },
  } = useRBAC(adminPermissions.settings.webhooks);
  const toggleNotification = useNotification();
  const isMounted = useRef(true);
  const { formatMessage } = useIntl();
  const [showModal, setShowModal] = useState(false);
  const [{ webhooks, webhooksToDelete, webhookToDelete, loadingWebhooks }, dispatch] = useReducer(
    reducer,
    initialState
  );
  const { notifyStatus } = useNotifyAT();

  useFocusWhenNavigate();
  const { push } = useHistory();
  const { pathname } = useLocation();
  const rowsCount = webhooks.length;
  const webhooksToDeleteLength = webhooksToDelete.length;
  const getWebhookIndex = id => webhooks.findIndex(webhook => webhook.id === id);

  useEffect(() => {
    isMounted.current = true;

    return () => {
      isMounted.current = false;
    };
  }, []);

  useEffect(() => {
    if (canRead) {
      fetchWebHooks();
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [canRead]);

  const fetchWebHooks = async () => {
    try {
      const { data } = await request('/admin/webhooks', {
        method: 'GET',
      });

      if (isMounted.current) {
        dispatch({
          type: 'GET_DATA_SUCCEEDED',
          data,
        });
        notifyStatus('webhooks have been loaded');
      }
    } catch (err) {
      console.log(err);

      if (isMounted.current) {
        if (err.code !== 20) {
          toggleNotification({
            type: 'warning',
            message: { id: 'notification.error' },
          });
        }
        dispatch({
          type: 'TOGGLE_LOADING',
        });
      }
    }
  };

  const handleToggleModal = () => {
    setShowModal(prev => !prev);
  };

  const handleConfirmDelete = () => {
    if (webhookToDelete) {
      handleConfirmDeleteOne();
    } else {
      handleConfirmDeleteAll();
    }
  };

  const handleConfirmDeleteOne = async () => {
    try {
      await request(`/admin/webhooks/${webhookToDelete}`, {
        method: 'DELETE',
      });

      dispatch({
        type: 'WEBHOOK_DELETED',
        index: getWebhookIndex(webhookToDelete),
      });
    } catch (err) {
      if (err.code !== 20) {
        toggleNotification({
          type: 'warning',
          message: { id: 'notification.error' },
        });
      }
    }
    setShowModal(false);
  };

  const handleConfirmDeleteAll = async () => {
    const body = {
      ids: webhooksToDelete,
    };

    try {
      await request('/admin/webhooks/batch-delete', {
        method: 'POST',
        body,
      });

      if (isMounted.current) {
        dispatch({
          type: 'WEBHOOKS_DELETED',
        });
      }
    } catch (err) {
      if (isMounted.current) {
        if (err.code !== 20) {
          toggleNotification({
            type: 'warning',
            message: { id: 'notification.error' },
          });
        }
      }
    }
    setShowModal(false);
  };

  const handleDeleteClick = id => {
    setShowModal(true);

    if (id !== 'all') {
      dispatch({
        type: 'SET_WEBHOOK_TO_DELETE',
        id,
      });
    }
  };

  const handleEnabledChange = async (value, id) => {
    const webhookIndex = getWebhookIndex(id);
    const initialWebhookProps = webhooks[webhookIndex];
    const keys = [webhookIndex, 'isEnabled'];

    const body = {
      ...initialWebhookProps,
      isEnabled: value,
    };

    delete body.id;

    try {
      dispatch({
        type: 'SET_WEBHOOK_ENABLED',
        keys,
        value,
      });

      await request(`/admin/webhooks/${id}`, {
        method: 'PUT',
        body,
      });
    } catch (err) {
      if (isMounted.current) {
        dispatch({
          type: 'SET_WEBHOOK_ENABLED',
          keys,
          value: !value,
        });

        if (err.code !== 20) {
          toggleNotification({
            type: 'warning',
            message: { id: 'notification.error' },
          });
        }
      }
    }
  };

  const handleSelectAllCheckbox = () => {
    dispatch({
      type: 'SET_ALL_WEBHOOKS_TO_DELETE',
    });
  };

  const handleSelectOneCheckbox = (value, id) => {
    dispatch({
      type: 'SET_WEBHOOKS_TO_DELETE',
      value,
      id,
    });
  };

  const handleGoTo = to => {
    push(`${pathname}/${to}`);
  };

  return (
    <Layout>
      <SettingsPageTitle name="Webhooks" />

        <HeaderLayout
          title={formatMessage({ id: 'Settings.webhooks.title', defaultMessage: 'Webhooks' })}
          subtitle={formatMessage({
            id: 'Settings.webhooks.list.description',
            defaultMessage: 'Get POST changes notifications.',
          })}
          primaryAction={
            canCreate &&
            !loadingWebhooks && (
              <LinkButton startIcon={<Plus />} variant="default" to={`${pathname}/create`} size="L">
                {formatMessage({
                  id: 'Settings.webhooks.list.button.add',
                  defaultMessage: 'Add new webhook',
                })}
              </LinkButton>
            )
          }
        />
        {webhooksToDeleteLength > 0 && canDelete && (
          <ActionLayout
            startActions={
              <>
                <Typography variant="epsilon" textColor="neutral600">
                  {formatMessage(
                    {
                      id: 'Settings.webhooks.to.delete',
                      defaultMessage:
                        '{webhooksToDeleteLength, plural, one {# asset} other {# assets}} selected',
                    },
                    { webhooksToDeleteLength }
                  )}
                </Typography>
                <Button
                  onClick={() => handleDeleteClick('all')}
                  startIcon={<Trash />}
                  size="L"
                  variant="danger-light"
                >
                  Delete
                </Button>
              </>
            }
          />
        )}
        <ContentLayout>
          {isLoading || loadingWebhooks ? (
            <Box background="neutral0" padding={6} shadow="filterShadow" hasRadius>
              <LoadingIndicatorPage />
            </Box>
          ) : (
            <>
              {rowsCount > 0 ? (
                <Table
                  colCount={5}
                  rowCount={rowsCount + 1}
                  footer={
                    <TFooter
                      onClick={() => (canCreate ? handleGoTo('create') : {})}
                      icon={<Plus />}
                    >
                      {formatMessage({
                        id: 'Settings.webhooks.list.button.add',
                        defaultMessage: 'Add new webhook',
                      })}
                    </TFooter>
                  }
                >

                        <BaseCheckbox
                          aria-label={formatMessage({
                            id: 'Settings.webhooks.list.all-entries.select',
                            defaultMessage: 'Select all entries',
                          })}
                          indeterminate={
                            webhooksToDeleteLength > 0 && webhooksToDeleteLength < rowsCount
                          }
                          value={webhooksToDeleteLength === rowsCount}
                          onValueChange={handleSelectAllCheckbox}
                        />

                        <Typography variant="sigma" textColor="neutral600">
                          {formatMessage({
                            id: 'Settings.webhooks.form.name',
                            defaultMessage: 'Name',
                          })}
                        </Typography>

                        <Typography variant="sigma" textColor="neutral600">
                          {formatMessage({
                            id: 'Settings.webhooks.form.url',
                            defaultMessage: 'URL',
                          })}
                        </Typography>

                        <Typography variant="sigma" textColor="neutral600">
                          {formatMessage({
                            id: 'Settings.webhooks.list.th.status',
                            defaultMessage: 'Status',
                          })}
                        </Typography>

                        <VisuallyHidden>
                          {formatMessage({
                            id: 'Settings.webhooks.list.th.actions',
                            defaultMessage: 'Actions',
                          })}
                        </VisuallyHidden>

                    {webhooks.map(webhook => (

 handleGoTo(webhook.id),
                          condition: canUpdate,
                        })}
                      >

                          <BaseCheckbox
                            aria-label={`${formatMessage({
                              id: 'Settings.webhooks.list.select',
                              defaultMessage: 'Select',
                            })} ${webhook.name}`}
                            value={webhooksToDelete?.includes(webhook.id)}
                            onValueChange={value => handleSelectOneCheckbox(value, webhook.id)}
                            id="select"
                            name="select"
                          />

                          <Typography fontWeight="semiBold" textColor="neutral800">
                            {webhook.name}
                          </Typography>

                          <Typography textColor="neutral800">{webhook.url}</Typography>

                          <Flex {...stopPropagation}>
                            <Switch
                              onLabel={formatMessage({
                                id: 'Settings.webhooks.enabled',
                                defaultMessage: 'Enabled',
                              })}
                              offLabel={formatMessage({
                                id: 'Settings.webhooks.disabled',
                                defaultMessage: 'Disabled',
                              })}
                              label={`${webhook.name} ${formatMessage({
                                id: 'Settings.webhooks.list.th.status',
                                defaultMessage: 'Status',
                              })}`}
                              selected={webhook.isEnabled}
                              onChange={() => handleEnabledChange(!webhook.isEnabled, webhook.id)}
                              visibleLabels
                            />
                          </Flex>

                          <Stack horizontal size={1} {...stopPropagation}>
                            {canUpdate && (
                              <IconButton
                                onClick={() => {
                                  handleGoTo(webhook.id);
                                }}
                                label={formatMessage({
                                  id: 'Settings.webhooks.events.update',
                                  defaultMessage: 'Update',
                                })}
                                icon={<Pencil />}
                                noBorder
                              />
                            )}
                            {canDelete && (
                              <IconButton
                                onClick={() => handleDeleteClick(webhook.id)}
                                label={formatMessage({
                                  id: 'Settings.webhooks.events.delete',
                                  defaultMessage: 'Delete',
                                })}
                                icon={<Trash />}
                                noBorder
                                id={`delete-${webhook.id}`}
                              />
                            )}
                          </Stack>

                    ))}

              ) : (
                <EmptyStateLayout
                  icon={<EmptyDocuments width="160px" />}
                  content={formatMessage({
                    id: 'Settings.webhooks.list.empty.description',
                    defaultMessage: 'Add your first webhook',
                  })}
                  action={
                    <Button
                      variant="secondary"
                      startIcon={<Plus />}
                      onClick={() => (canCreate ? handleGoTo('create') : {})}
                    >
                      {formatMessage({
                        id: 'Settings.webhooks.list.button.add',
                        defaultMessage: 'Add new webhook',
                      })}
                    </Button>
                  }
                />
              )}
            </>
          )}
        </ContentLayout>

      <ConfirmDialog
        isOpen={showModal}
        onToggleDialog={handleToggleModal}
        onConfirm={handleConfirmDelete}
      />
    </Layout>
  );
};

export default ListView;
<|endoftext|>


<commit_before>/**
 * @update zhixin wen <wenzhixin2010@gmail.com>
 */

const Utils = $.fn.bootstrapTable.utils

function printPageBuilderDefault (table) {
  return `

  <title>Print Table</title>

Printed on: ${new Date}

${table}

  `
}

$.extend($.fn.bootstrapTable.locales, {
  formatPrint () {
    return 'Print'
  }
})
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)

$.extend($.fn.bootstrapTable.defaults, {
  showPrint: false,
  printAsFilteredAndSortedOnUI: true,
  printSortColumn: undefined,
  printSortOrder: 'asc',
  printPageBuilder (table) {
    return printPageBuilderDefault(table)
  }
})

$.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
  printFilter: undefined,
  printIgnore: false,
  printFormatter: undefined
})

$.extend($.fn.bootstrapTable.defaults.icons, {
  print: {
    bootstrap3: 'glyphicon-print icon-share',
    'bootstrap-table': 'icon-printer'
  }[$.fn.bootstrapTable.theme] || 'fa-print'
})

$.BootstrapTable = class extends $.BootstrapTable {
  init (...args) {
    super.init(...args)

    if (!this.options.showPrint) {
      return
    }

    this.mergedCells = []
  }

  initToolbar (...args) {
    this.showToolbar = this.showToolbar || this.options.showPrint

    if (this.options.showPrint) {
      this.buttons = Object.assign(this.buttons, {
        print: {
          text: this.options.formatPrint(),
          icon: this.options.icons.print,
          event: () => {
            this.doPrint(this.options.printAsFilteredAndSortedOnUI ? this.getData() : this.options.data.slice(0))
          },
          attributes: {
            'aria-label': this.options.formatPrint(),
            title: this.options.formatPrint()
          }
        }
      })
    }

    super.initToolbar(...args)
  }

  mergeCells (options) {
    super.mergeCells(options)

    if (!this.options.showPrint) {
      return
    }

    let col = this.getVisibleFields().indexOf(options.field)

    if (Utils.hasDetailViewIcon(this.options)) {
      col += 1
    }

    this.mergedCells.push({
      row: options.index,
      col,
      rowspan: options.rowspan || 1,
      colspan: options.colspan || 1
    })
  }

  doPrint (data) {
    const formatValue = (row, i, column) => {
      const value = Utils.calculateObjectValue(column, column.printFormatter,
        [row[column.field], row, i], row[column.field])

      return typeof value === 'undefined' || value === null ?
        this.options.undefinedText : value
    }

    const buildTable = (data, columnsArray) => {
      const dir = this.$el.attr('dir') || 'ltr'
      const html = [`<table dir="${dir}"><thead>`]

      for (const columns of columnsArray) {
        html.push('
')
        for (let h = 0; h < columns.length; h++) {
          if (!columns[h].printIgnore) {
            html.push(
              `${columns[h].title}	`)
          }
        }
        html.push('
')
      }

      html.push('

')

      const dontRender = []

      if (this.mergedCells) {
        for (let mc = 0; mc < this.mergedCells.length; mc++) {
          const currentMergedCell = this.mergedCells[mc]

          for (let rs = 0; rs < currentMergedCell.rowspan; rs++) {
            const row = currentMergedCell.row + rs

            for (let cs = 0; cs < currentMergedCell.colspan; cs++) {
              const col = currentMergedCell.col + cs

              dontRender.push(`${row },${ col}`)
            }
          }
        }
      }

      for (let i = 0; i < data.length; i++) {
        html.push('
')

        const columns = columnsArray.flat(1)

        columns.sort((c1, c2) => {
          return c1.colspanIndex - c2.colspanIndex
        })

        for (let j = 0; j < columns.length; j++) {
          if (columns[j].colspanGroup > 0) continue

          let rowspan = 0
          let colspan = 0

          if (this.mergedCells) {
            for (let mc = 0; mc < this.mergedCells.length; mc++) {
              const currentMergedCell = this.mergedCells[mc]

              if (currentMergedCell.col === j && currentMergedCell.row === i) {
                rowspan = currentMergedCell.rowspan
                colspan = currentMergedCell.colspan
              }
            }
          }

          if (
            !columns[j].printIgnore && columns[j].field &&
              (
                !dontRender.includes(`${i },${ j}`) ||
                (rowspan > 0 && colspan > 0)
              )
          ) {
            if (rowspan > 0 && colspan > 0) {
              html.push(`<td ${Utils.sprintf(' rowspan="%s"', rowspan)} ${Utils.sprintf(' colspan="%s"', colspan)}>`, formatValue(data[i], i, columns[j]), '	')
            } else {
              html.push('', formatValue(data[i], i, columns[j]), '	')
            }
          }
        }

        html.push('
')
      }

      html.push('
')
      if (this.options.showFooter) {
        html.push('
')

        for (const columns of columnsArray) {
          for (let h = 0; h < columns.length; h++) {
            if (!columns[h].printIgnore) {
              const footerData = Utils.trToData(columns, this.$el.find('>tfoot>tr'))
              const footerValue = Utils.calculateObjectValue(columns[h], columns[h].footerFormatter, [data], footerData[0] && footerData[0][columns[h].field] || '')

              html.push(`<th>${footerValue}</th>`)
            }
          }
        }

        html.push('
')
      }
      html.push('
')
      return html.join('')
    }

    const sortRows = (data, colName, sortOrder) => {
      if (!colName) {
        return data
      }
      let reverse = sortOrder !== 'asc'

      reverse = -((+reverse) || -1)
      return data.sort((a, b) => reverse * (a[colName].localeCompare(b[colName])))
    }

    const filterRow = (row, filters) => {
      for (let index = 0; index < filters.length; ++index) {
        if (row[filters[index].colName] !== filters[index].value) {
          return false
        }
      }
      return true
    }

    const filterRows = (data, filters) => data.filter(row => filterRow(row, filters))
    const getColumnFilters = columns => !columns || !columns[0] ? [] : columns[0].filter(col => col.printFilter).map(col => ({
      colName: col.field,
      value: col.printFilter
    }))

    data = filterRows(data, getColumnFilters(this.options.columns))
    data = sortRows(data, this.options.printSortColumn, this.options.printSortOrder)
    const table = buildTable(data, this.options.columns)
    const newWin = window.open('')

    newWin.document.write(this.options.printPageBuilder.call(this, table))
    newWin.document.close()
    newWin.focus()
    newWin.print()
    newWin.close()
  }
}
<commit_msg>Fixed print formatter bug<commit_after>/**
 * @update zhixin wen <wenzhixin2010@gmail.com>
 */

const Utils = $.fn.bootstrapTable.utils

function printPageBuilderDefault (table) {
  return `

  <title>Print Table</title>

Printed on: ${new Date}

${table}

  `
}

$.extend($.fn.bootstrapTable.locales, {
  formatPrint () {
    return 'Print'
  }
})
$.extend($.fn.bootstrapTable.defaults, $.fn.bootstrapTable.locales)

$.extend($.fn.bootstrapTable.defaults, {
  showPrint: false,
  printAsFilteredAndSortedOnUI: true,
  printSortColumn: undefined,
  printSortOrder: 'asc',
  printPageBuilder (table) {
    return printPageBuilderDefault(table)
  }
})

$.extend($.fn.bootstrapTable.COLUMN_DEFAULTS, {
  printFilter: undefined,
  printIgnore: false,
  printFormatter: undefined
})

$.extend($.fn.bootstrapTable.defaults.icons, {
  print: {
    bootstrap3: 'glyphicon-print icon-share',
    'bootstrap-table': 'icon-printer'
  }[$.fn.bootstrapTable.theme] || 'fa-print'
})

$.BootstrapTable = class extends $.BootstrapTable {
  init (...args) {
    super.init(...args)

    if (!this.options.showPrint) {
      return
    }

    this.mergedCells = []
  }

  initToolbar (...args) {
    this.showToolbar = this.showToolbar || this.options.showPrint

    if (this.options.showPrint) {
      this.buttons = Object.assign(this.buttons, {
        print: {
          text: this.options.formatPrint(),
          icon: this.options.icons.print,
          event: () => {
            this.doPrint(this.options.printAsFilteredAndSortedOnUI ? this.getData() : this.options.data.slice(0))
          },
          attributes: {
            'aria-label': this.options.formatPrint(),
            title: this.options.formatPrint()
          }
        }
      })
    }

    super.initToolbar(...args)
  }

  mergeCells (options) {
    super.mergeCells(options)

    if (!this.options.showPrint) {
      return
    }

    let col = this.getVisibleFields().indexOf(options.field)

    if (Utils.hasDetailViewIcon(this.options)) {
      col += 1
    }

    this.mergedCells.push({
      row: options.index,
      col,
      rowspan: options.rowspan || 1,
      colspan: options.colspan || 1
    })
  }

  doPrint (data) {
    const formatValue = (row, i, column) => {
      const value = Utils.calculateObjectValue(column,
        column.printFormatter || column.formatter,
        [row[column.field], row, i], row[column.field])

      return typeof value === 'undefined' || value === null ?
        this.options.undefinedText : value
    }

    const buildTable = (data, columnsArray) => {
      const dir = this.$el.attr('dir') || 'ltr'
      const html = [`<table dir="${dir}"><thead>`]

      for (const columns of columnsArray) {
        html.push('
')
        for (let h = 0; h < columns.length; h++) {
          if (!columns[h].printIgnore) {
            html.push(
              `${columns[h].title}	`)
          }
        }
        html.push('
')
      }

      html.push('

')

      const dontRender = []

      if (this.mergedCells) {
        for (let mc = 0; mc < this.mergedCells.length; mc++) {
          const currentMergedCell = this.mergedCells[mc]

          for (let rs = 0; rs < currentMergedCell.rowspan; rs++) {
            const row = currentMergedCell.row + rs

            for (let cs = 0; cs < currentMergedCell.colspan; cs++) {
              const col = currentMergedCell.col + cs

              dontRender.push(`${row },${ col}`)
            }
          }
        }
      }

      for (let i = 0; i < data.length; i++) {
        html.push('
')

        const columns = columnsArray.flat(1)

        columns.sort((c1, c2) => {
          return c1.colspanIndex - c2.colspanIndex
        })

        for (let j = 0; j < columns.length; j++) {
          if (columns[j].colspanGroup > 0) continue

          let rowspan = 0
          let colspan = 0

          if (this.mergedCells) {
            for (let mc = 0; mc < this.mergedCells.length; mc++) {
              const currentMergedCell = this.mergedCells[mc]

              if (currentMergedCell.col === j && currentMergedCell.row === i) {
                rowspan = currentMergedCell.rowspan
                colspan = currentMergedCell.colspan
              }
            }
          }

          if (
            !columns[j].printIgnore && columns[j].field &&
              (
                !dontRender.includes(`${i },${ j}`) ||
                (rowspan > 0 && colspan > 0)
              )
          ) {
            if (rowspan > 0 && colspan > 0) {
              html.push(`<td ${Utils.sprintf(' rowspan="%s"', rowspan)} ${Utils.sprintf(' colspan="%s"', colspan)}>`, formatValue(data[i], i, columns[j]), '	')
            } else {
              html.push('', formatValue(data[i], i, columns[j]), '	')
            }
          }
        }

        html.push('
')
      }

      html.push('
')
      if (this.options.showFooter) {
        html.push('
')

        for (const columns of columnsArray) {
          for (let h = 0; h < columns.length; h++) {
            if (!columns[h].printIgnore) {
              const footerData = Utils.trToData(columns, this.$el.find('>tfoot>tr'))
              const footerValue = Utils.calculateObjectValue(columns[h], columns[h].footerFormatter, [data], footerData[0] && footerData[0][columns[h].field] || '')

              html.push(`<th>${footerValue}</th>`)
            }
          }
        }

        html.push('
')
      }
      html.push('
')
      return html.join('')
    }

    const sortRows = (data, colName, sortOrder) => {
      if (!colName) {
        return data
      }
      let reverse = sortOrder !== 'asc'

      reverse = -((+reverse) || -1)
      return data.sort((a, b) => reverse * (a[colName].localeCompare(b[colName])))
    }

    const filterRow = (row, filters) => {
      for (let index = 0; index < filters.length; ++index) {
        if (row[filters[index].colName] !== filters[index].value) {
          return false
        }
      }
      return true
    }

    const filterRows = (data, filters) => data.filter(row => filterRow(row, filters))
    const getColumnFilters = columns => !columns || !columns[0] ? [] : columns[0].filter(col => col.printFilter).map(col => ({
      colName: col.field,
      value: col.printFilter
    }))

    data = filterRows(data, getColumnFilters(this.options.columns))
    data = sortRows(data, this.options.printSortColumn, this.options.printSortOrder)
    const table = buildTable(data, this.options.columns)
    const newWin = window.open('')

    newWin.document.write(this.options.printPageBuilder.call(this, table))
    newWin.document.close()
    newWin.focus()
    newWin.print()
    newWin.close()
  }
}
<|endoftext|>


<commit_before>var _ = require('underscore');
var os = require('os');

var utils = require('./utils.js');

/* Meteor's current architecture scheme defines the following virtual
 * machine types, which are defined by specifying what is promised by
 * the host environment:
 *
 * browser.w3c
 *   A web browser compliant with modern standards. This is
 *   intentionally a broad definition. In the coming years, as web
 *   standards evolve, we will likely tighten it up.
 *
 * browser.ie[678]
 *   Old versions of Internet Explorer (not sure yet exactly which
 *   versions to distinguish -- maybe 6 and 8?)
 *
 * os.linux.x86_32
 * os.linux.x86_64
 *   Linux on Intel x86 architecture. x86_64 means a system that can
 *   run 64-bit images, furnished with 64-bit builds of shared
 *   libraries (there is no guarantee that 32-bit builds of shared
 *   libraries will be available). x86_32 means a system that can run
 *   32-bit images, furnished with 32-bit builds of shared libraries.
 *   Additionally, if a package contains shared libraries (for use by
 *   other packages), then if the package is built for x86_64, it
 *   should contain a 64-bit version of the library, and likewise for
 *   32-bit.
 *
 *   Operationally speaking, if you worked at it, under this
 *   definition it would be possible to build a Linux system that can
 *   run both x86_64 and x86_32 images (eg, by using a 64-bit kernel
 *   and making sure that both versions of all relevant libraries were
 *   installed). But we require such a host to decide whether it is
 *   x86_64 or x86_32, and stick with it. You can't load a combination
 *   of packages from each and expect them to work together, because
 *   if they contain shared libraries they all need to have the same
 *   architecture.
 *
 *   Basically the punchline is: if you installed the 32-bit version
 *   of Ubuntu, you've got a os.linux.x86_32 system and you will
 *   use exclusively os.linux.x86_32 packages, and likewise
 *   64-bit. They are two parallel universes and which one you're in
 *   is determined by which version of Red Hat or Ubuntu you
 *   installed.
 *
 * os.osx.x86_64
 *   OS X (technically speaking, Darwin) on Intel x86 architecture,
 *   with a kernel capable of loading 64-bit images, and 64-bit builds
 *   of shared libraries available.  If a os.osx.x86_64 package
 *   contains a shared library, it is only required to provide a
 *   64-bit version of the library (it is not required to provide a
 *   fat binary with both 32-bit and 64-bit builds).
 *
 *   Note that in modern Darwin, both the 32 and 64 bit versions of
 *   the kernel can load 64-bit images, and the Apple-supplied shared
 *   libraries are fat binaries that include both 32-bit and 64-bit
 *   builds in a single file. So it is technically fine (but
 *   discouraged) for a os.osx.x86_64 to include a 32-bit
 *   executable, if it only uses the system's shared libraries, but
 *   you'll run into problems if shared libraries from other packages
 *   are used.
 *
 *   There is no os.osx.x86_32. Our experience is that such
 *   hardware is virtually extinct. Meteor has never supported it and
 *   nobody has asked for it.
 *
 * os.windows.x86_32
 * os.windows.x86_64
 *   Once, on the far side of yesterday, there was not a 64-bit
 *   build of Meteor for Windows, due to the belief that Node didn't
 *   take (enough?) advantage of a 64-bit platform.  As time has passed,
 *   and as V8 engine improvements have been bestowed upon it, this is
 *   no longer as clear as it may have once been.  Node.js Foundation
 *   releases 64-bit versions themselves, likely for good reason.
 *   Present-day operation of 64-bit binaries on 64-bit Windows
 *   platforms show clear performance benefits over their 32-bit
 *   siblings (e.g. 7-zip, et.al), so Meteor should also try to offer
 *   that same benefit by building and offering a 64-bit version.
 *
 * To be (more but far from completely) precise, the ABI for os.*
 * architectures includes a CPU type, a mode in which the code will be
 * run (eg, 64 bit), an executable file format (eg, ELF), a promise to
 * make any shared libraries available in a particular architecture,
 * and promise to set up the shared library search path
 * "appropriately". In the future it will also include some guarantees
 * about the directory layout in the environment, eg, location of a
 * directory where temporary files may be freely written. It does not
 * include any syscalls (beyond those used by code that customarily is
 * statically linked into every executable built on a platform, eg,
 * exit(2)). It does not guarantee the presence of any particular
 * shared libraries or programs (including any particular shell or
 * traditional tools like 'grep' or 'find').
 *
 * To model the shared libraries that are required on a system (and
 * the particular versions that are required), and to model
 * dependencies on command-line programs like 'bash' and 'grep', the
 * idea is to have a package named something like 'posix-base' that
 * rolls up a reasonable base environment (including such modern
 * niceties as libopenssl) and is supplied by the container. This
 * allows it to be versioned, unlike architectures, which we hope to
 * avoid versioning.
 *
 * Q: What does "x86" mean?
 * A: It refers to the traditional Intel architecture, which
 * originally surfaced in CPUs such as the 8086 and the 80386. Those
 * of us who are older should remember that the last time that Intel
 * used this branding was the 80486, introduced in 1989, and that
 * today, parts that use this architecture bear names like "Core",
 * "Atom", and "Phenom", with no "86" it sight. We use it in the
 * architecture name anyway because we don't want to depart too far
 * from Linux's architecture names.
 *
 * Q: Why do we call it "x86_32" instead of the customary "i386" or
 * "i686"?
 * A: We wanted to have one name for 32-bit and one name for 64-bit,
 * rather than several names for each that are virtual synonyms for
 * each (eg, x86_64 vs amd64 vs ia64, i386 vs i686 vs x86). For the
 * moment anyway, we're willing to adopt a "one size fits all"
 * attitude to get there (no ability to have separate builds for 80386
 * CPUs that don't support Pentium Pro extensions, for example --
 * you'll have to do runtime detection if you need that). And as long
 * as we have to pick a name, we wanted to pick one that was super
 * clear (it is not obvious to many people that "i686" means "32-bit
 * Intel", because why should it be?) and didn't imply too close of an
 * equivalence to the precise meanings that other platforms may assign
 * to some of these strings.
 */

// Returns the fully qualified arch of this host -- something like
// "os.linux.x86_32" or "os.osx.x86_64". Must be called inside
// a fiber. Throws an error if it's not a supported architecture.
//
// If you change this, also change scripts/admin/launch-meteor
var _host = null; // memoize
var host = function () {
  if (! _host) {
    var run = function (...args) {
      var result = utils.execFileSync(args[0], args.slice(1)).stdout;
      if (! result) {
        throw new Error("can't get arch with " + args.join(" ") + "?");
      }
      return result.replace(/\s*$/, ''); // trailing whitespace
    };

    var platform = os.platform();

    if (platform === "darwin") {
      // Can't just test uname -m = x86_64, because Snow Leopard can
      // return other values.
      if (run('uname', '-p') !== "i386" ||
          run('sysctl', '-n', 'hw.cpu64bit_capable') !== "1") {
        throw new Error("Only 64-bit Intel processors are supported on OS X");
      }
      _host  = "os.osx.x86_64";
    }

    else if (platform === "linux") {
      var machine = run('uname', '-m');
      if (_.contains(["i386", "i686", "x86"], machine)) {
        _host = "os.linux.x86_32";
      } else if (_.contains(["x86_64", "amd64", "ia64"], machine)) {
        _host = "os.linux.x86_64";
      } else {
        throw new Error("Unsupported architecture: " + machine);
      }
    }

    else if (platform === "win32") {
      if (process.arch === "x64") {
        _host = "os.windows.x86_64";
      } else {
        _host = "os.windows.x86_32";
      }
    } else {
      throw new Error("Unsupported operating system: " + platform);
    }
  }

  return _host;
};

// In order to springboard to earlier Meteor releases that did not have
// 64-bit Windows builds, Windows installations must be allowed to
// download 32-bit builds of meteor-tool.
exports.acceptableMeteorToolArches = function () {
  if (os.platform() === "win32") {
    switch (utils.architecture()) {
    case "x86_32":
      return ["os.windows.x86_32"];
    case "x86_64":
      return [
        "os.windows.x86_64",
        "os.windows.x86_32",
      ];
    }
  }

  return [host()];
};

// 64-bit Windows machines that have been using a 32-bit version of Meteor
// are eligible to switch to 64-bit beginning with Meteor 1.6, which is
// the first version of Meteor that contains this code.
exports.canSwitchTo64Bit = function () {
  return utils.architecture() === "x86_64" &&
    host() === "os.windows.x86_32";
};

// True if `host` (an architecture name such as 'os.linux.x86_64') can run
// programs of architecture `program` (which might be something like 'os',
// 'os.linux', or 'os.linux.x86_64').
//
// `host` and `program` are just mnemonics -- `host` does not
// necessarily have to be a fully qualified architecture name. This
// function just checks to see if `program` describes a set of
// environments that is a (non-strict) superset of `host`.
var matches = function (host, program) {
  return host.substr(0, program.length) === program &&
    (host.length === program.length ||
     host.substr(program.length, 1) === ".");
};

// Like `supports`, but instead taken an array of possible
// architectures as its second argument. Returns the most specific
// match, or null if none match. Throws an error if `programs`
// contains exact duplicates.
var mostSpecificMatch = function (host, programs) {
  var seen = {};
  var best = null;

  _.each(programs, function (p) {
    if (seen[p]) {
      throw new Error("Duplicate architecture: " + p);
    }
    seen[p] = true;
    if (archinfo.matches(host, p) &&
        (! best || p.length > best.length)) {
      best = p;
    }
  });

  return best;
};

// `programs` is a set of architectures (as an array of string, which
// may contain duplicates). Determine if there exists any architecture
// that is compatible with all of the architectures in the set. If so,
// returns the least specific such architecture. Otherwise (the
// architectures are disjoin) raise an exception.
//
// For example, for 'os' and 'os.osx', return 'os.osx'. For 'os' and
// 'os.linux.x86_64', return 'os.linux.x86_64'. For 'os' and 'browser', throw an
// exception.
var leastSpecificDescription = function (programs) {
  if (programs.length === 0) {
    return '';
  }

  // Find the longest string
  var longest = _.max(programs, function (p) { return p.length; });

  // If everything else in the list is compatible with the longest,
  // then it must be the most specific, and if everything is
  // compatible with the most specific then it must be the least
  // specific compatible description.
  _.each(programs, function (p) {
    if (! archinfo.matches(longest, p)) {
      throw new Error("Incompatible architectures: '" + p + "' and '" +
                      longest + "'");
    }
  });

  return longest;
};

var withoutSpecificOs = function (arch) {
  if (arch.substr(0, 3) === 'os.') {
    return 'os';
  }
  return arch;
};

var archinfo = exports;
_.extend(archinfo, {
  host: host,
  matches: matches,
  mostSpecificMatch: mostSpecificMatch,
  leastSpecificDescription: leastSpecificDescription,
  withoutSpecificOs: withoutSpecificOs
});
<commit_msg>Disable automatic switching to 64-bit meteor-tool on Windows.<commit_after>var _ = require('underscore');
var os = require('os');

var utils = require('./utils.js');

/* Meteor's current architecture scheme defines the following virtual
 * machine types, which are defined by specifying what is promised by
 * the host environment:
 *
 * browser.w3c
 *   A web browser compliant with modern standards. This is
 *   intentionally a broad definition. In the coming years, as web
 *   standards evolve, we will likely tighten it up.
 *
 * browser.ie[678]
 *   Old versions of Internet Explorer (not sure yet exactly which
 *   versions to distinguish -- maybe 6 and 8?)
 *
 * os.linux.x86_32
 * os.linux.x86_64
 *   Linux on Intel x86 architecture. x86_64 means a system that can
 *   run 64-bit images, furnished with 64-bit builds of shared
 *   libraries (there is no guarantee that 32-bit builds of shared
 *   libraries will be available). x86_32 means a system that can run
 *   32-bit images, furnished with 32-bit builds of shared libraries.
 *   Additionally, if a package contains shared libraries (for use by
 *   other packages), then if the package is built for x86_64, it
 *   should contain a 64-bit version of the library, and likewise for
 *   32-bit.
 *
 *   Operationally speaking, if you worked at it, under this
 *   definition it would be possible to build a Linux system that can
 *   run both x86_64 and x86_32 images (eg, by using a 64-bit kernel
 *   and making sure that both versions of all relevant libraries were
 *   installed). But we require such a host to decide whether it is
 *   x86_64 or x86_32, and stick with it. You can't load a combination
 *   of packages from each and expect them to work together, because
 *   if they contain shared libraries they all need to have the same
 *   architecture.
 *
 *   Basically the punchline is: if you installed the 32-bit version
 *   of Ubuntu, you've got a os.linux.x86_32 system and you will
 *   use exclusively os.linux.x86_32 packages, and likewise
 *   64-bit. They are two parallel universes and which one you're in
 *   is determined by which version of Red Hat or Ubuntu you
 *   installed.
 *
 * os.osx.x86_64
 *   OS X (technically speaking, Darwin) on Intel x86 architecture,
 *   with a kernel capable of loading 64-bit images, and 64-bit builds
 *   of shared libraries available.  If a os.osx.x86_64 package
 *   contains a shared library, it is only required to provide a
 *   64-bit version of the library (it is not required to provide a
 *   fat binary with both 32-bit and 64-bit builds).
 *
 *   Note that in modern Darwin, both the 32 and 64 bit versions of
 *   the kernel can load 64-bit images, and the Apple-supplied shared
 *   libraries are fat binaries that include both 32-bit and 64-bit
 *   builds in a single file. So it is technically fine (but
 *   discouraged) for a os.osx.x86_64 to include a 32-bit
 *   executable, if it only uses the system's shared libraries, but
 *   you'll run into problems if shared libraries from other packages
 *   are used.
 *
 *   There is no os.osx.x86_32. Our experience is that such
 *   hardware is virtually extinct. Meteor has never supported it and
 *   nobody has asked for it.
 *
 * os.windows.x86_32
 * os.windows.x86_64
 *   Once, on the far side of yesterday, there was not a 64-bit
 *   build of Meteor for Windows, due to the belief that Node didn't
 *   take (enough?) advantage of a 64-bit platform.  As time has passed,
 *   and as V8 engine improvements have been bestowed upon it, this is
 *   no longer as clear as it may have once been.  Node.js Foundation
 *   releases 64-bit versions themselves, likely for good reason.
 *   Present-day operation of 64-bit binaries on 64-bit Windows
 *   platforms show clear performance benefits over their 32-bit
 *   siblings (e.g. 7-zip, et.al), so Meteor should also try to offer
 *   that same benefit by building and offering a 64-bit version.
 *
 * To be (more but far from completely) precise, the ABI for os.*
 * architectures includes a CPU type, a mode in which the code will be
 * run (eg, 64 bit), an executable file format (eg, ELF), a promise to
 * make any shared libraries available in a particular architecture,
 * and promise to set up the shared library search path
 * "appropriately". In the future it will also include some guarantees
 * about the directory layout in the environment, eg, location of a
 * directory where temporary files may be freely written. It does not
 * include any syscalls (beyond those used by code that customarily is
 * statically linked into every executable built on a platform, eg,
 * exit(2)). It does not guarantee the presence of any particular
 * shared libraries or programs (including any particular shell or
 * traditional tools like 'grep' or 'find').
 *
 * To model the shared libraries that are required on a system (and
 * the particular versions that are required), and to model
 * dependencies on command-line programs like 'bash' and 'grep', the
 * idea is to have a package named something like 'posix-base' that
 * rolls up a reasonable base environment (including such modern
 * niceties as libopenssl) and is supplied by the container. This
 * allows it to be versioned, unlike architectures, which we hope to
 * avoid versioning.
 *
 * Q: What does "x86" mean?
 * A: It refers to the traditional Intel architecture, which
 * originally surfaced in CPUs such as the 8086 and the 80386. Those
 * of us who are older should remember that the last time that Intel
 * used this branding was the 80486, introduced in 1989, and that
 * today, parts that use this architecture bear names like "Core",
 * "Atom", and "Phenom", with no "86" it sight. We use it in the
 * architecture name anyway because we don't want to depart too far
 * from Linux's architecture names.
 *
 * Q: Why do we call it "x86_32" instead of the customary "i386" or
 * "i686"?
 * A: We wanted to have one name for 32-bit and one name for 64-bit,
 * rather than several names for each that are virtual synonyms for
 * each (eg, x86_64 vs amd64 vs ia64, i386 vs i686 vs x86). For the
 * moment anyway, we're willing to adopt a "one size fits all"
 * attitude to get there (no ability to have separate builds for 80386
 * CPUs that don't support Pentium Pro extensions, for example --
 * you'll have to do runtime detection if you need that). And as long
 * as we have to pick a name, we wanted to pick one that was super
 * clear (it is not obvious to many people that "i686" means "32-bit
 * Intel", because why should it be?) and didn't imply too close of an
 * equivalence to the precise meanings that other platforms may assign
 * to some of these strings.
 */

// Returns the fully qualified arch of this host -- something like
// "os.linux.x86_32" or "os.osx.x86_64". Must be called inside
// a fiber. Throws an error if it's not a supported architecture.
//
// If you change this, also change scripts/admin/launch-meteor
var _host = null; // memoize
var host = function () {
  if (! _host) {
    var run = function (...args) {
      var result = utils.execFileSync(args[0], args.slice(1)).stdout;
      if (! result) {
        throw new Error("can't get arch with " + args.join(" ") + "?");
      }
      return result.replace(/\s*$/, ''); // trailing whitespace
    };

    var platform = os.platform();

    if (platform === "darwin") {
      // Can't just test uname -m = x86_64, because Snow Leopard can
      // return other values.
      if (run('uname', '-p') !== "i386" ||
          run('sysctl', '-n', 'hw.cpu64bit_capable') !== "1") {
        throw new Error("Only 64-bit Intel processors are supported on OS X");
      }
      _host  = "os.osx.x86_64";
    }

    else if (platform === "linux") {
      var machine = run('uname', '-m');
      if (_.contains(["i386", "i686", "x86"], machine)) {
        _host = "os.linux.x86_32";
      } else if (_.contains(["x86_64", "amd64", "ia64"], machine)) {
        _host = "os.linux.x86_64";
      } else {
        throw new Error("Unsupported architecture: " + machine);
      }
    }

    else if (platform === "win32") {
      if (process.arch === "x64") {
        _host = "os.windows.x86_64";
      } else {
        _host = "os.windows.x86_32";
      }
    } else {
      throw new Error("Unsupported operating system: " + platform);
    }
  }

  return _host;
};

// In order to springboard to earlier Meteor releases that did not have
// 64-bit Windows builds, Windows installations must be allowed to
// download 32-bit builds of meteor-tool.
exports.acceptableMeteorToolArches = function () {
  if (os.platform() === "win32") {
    switch (utils.architecture()) {
    case "x86_32":
      return ["os.windows.x86_32"];
    case "x86_64":
      return [
        "os.windows.x86_64",
        "os.windows.x86_32",
      ];
    }
  }

  return [host()];
};

// 64-bit Windows machines that have been using a 32-bit version of Meteor
// are eligible to switch to 64-bit beginning with Meteor 1.6, which is
// the first version of Meteor that contains this code.
exports.canSwitchTo64Bit = function () {
  // Automatically switching from 32-bit to 64-bit Windows builds is
  // disabled for the time being, since downloading additional builds of
  // meteor-tool isn't stable enough at the moment (on Windows, at least)
  // to introduce in a release candidate.
  return false &&
    utils.architecture() === "x86_64" &&
    host() === "os.windows.x86_32";
};

// True if `host` (an architecture name such as 'os.linux.x86_64') can run
// programs of architecture `program` (which might be something like 'os',
// 'os.linux', or 'os.linux.x86_64').
//
// `host` and `program` are just mnemonics -- `host` does not
// necessarily have to be a fully qualified architecture name. This
// function just checks to see if `program` describes a set of
// environments that is a (non-strict) superset of `host`.
var matches = function (host, program) {
  return host.substr(0, program.length) === program &&
    (host.length === program.length ||
     host.substr(program.length, 1) === ".");
};

// Like `supports`, but instead taken an array of possible
// architectures as its second argument. Returns the most specific
// match, or null if none match. Throws an error if `programs`
// contains exact duplicates.
var mostSpecificMatch = function (host, programs) {
  var seen = {};
  var best = null;

  _.each(programs, function (p) {
    if (seen[p]) {
      throw new Error("Duplicate architecture: " + p);
    }
    seen[p] = true;
    if (archinfo.matches(host, p) &&
        (! best || p.length > best.length)) {
      best = p;
    }
  });

  return best;
};

// `programs` is a set of architectures (as an array of string, which
// may contain duplicates). Determine if there exists any architecture
// that is compatible with all of the architectures in the set. If so,
// returns the least specific such architecture. Otherwise (the
// architectures are disjoin) raise an exception.
//
// For example, for 'os' and 'os.osx', return 'os.osx'. For 'os' and
// 'os.linux.x86_64', return 'os.linux.x86_64'. For 'os' and 'browser', throw an
// exception.
var leastSpecificDescription = function (programs) {
  if (programs.length === 0) {
    return '';
  }

  // Find the longest string
  var longest = _.max(programs, function (p) { return p.length; });

  // If everything else in the list is compatible with the longest,
  // then it must be the most specific, and if everything is
  // compatible with the most specific then it must be the least
  // specific compatible description.
  _.each(programs, function (p) {
    if (! archinfo.matches(longest, p)) {
      throw new Error("Incompatible architectures: '" + p + "' and '" +
                      longest + "'");
    }
  });

  return longest;
};

var withoutSpecificOs = function (arch) {
  if (arch.substr(0, 3) === 'os.') {
    return 'os';
  }
  return arch;
};

var archinfo = exports;
_.extend(archinfo, {
  host: host,
  matches: matches,
  mostSpecificMatch: mostSpecificMatch,
  leastSpecificDescription: leastSpecificDescription,
  withoutSpecificOs: withoutSpecificOs
});
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

const babel = require('@babel/core');
const path = require('path');

const TYPESCRIPT_EXTS = {
  '.ts': true,
  '.tsx': true,
};

function getDefaultPlugins(options: BabelOptions) {
  return [
    'jsx',
    TYPESCRIPT_EXTS[path.extname(options.filename || '')]
      ? 'typescript'
      : 'flow',
    'asyncGenerators',
    'bigInt',
    'classProperties',
    'classPrivateProperties',
    'classPrivateMethods',
    ['decorators', { decoratorsBeforeExport: false }],
    'doExpressions',
    'dynamicImport',
    'exportDefaultFrom',
    'exportNamespaceFrom',
    'functionBind',
    'functionSent',
    'importMeta',
    'logicalAssignment',
    'nullishCoalescingOperator',
    'numericSeparator',
    'objectRestSpread',
    'optionalCatchBinding',
    'optionalChaining',
    ['pipelineOperator', { proposal: 'minimal' }],
    'throwExpressions',
  ];
}

export type Parser = {
  parse: (src: string) => ASTNode,
};

type ParserOptions = {
  plugins?: Array<string | [string, {}]>,
  tokens?: boolean,
};

type BabelOptions = {
  cwd?: string,
  filename?: string,
  envName?: string,
  babelrc?: boolean,
  root?: string,
  rootMode?: string,
  configFile?: string | false,
  babelrcRoots?: true | string | string[],
};

export type Options = BabelOptions & {
  parserOptions?: ParserOptions,
};

function buildOptions(
  parserOptions: ?ParserOptions,
  babelOptions: BabelOptions,
): ParserOptions {
  let parserOpts = {
    plugins: [],
  };

  if (parserOptions) {
    parserOpts = {
      ...parserOptions,
      plugins: parserOptions.plugins ? [...parserOptions.plugins] : [],
    };
  }

  const partialConfig = babel.loadPartialConfig(babelOptions);

  if (!partialConfig.hasFilesystemConfig() && parserOpts.plugins.length === 0) {
    parserOpts.plugins = getDefaultPlugins(babelOptions);
  }

  // Ensure we always have estree plugin enabled, if we add it a second time
  // here it does not matter
  parserOpts.plugins.push('estree');

  return parserOpts;
}

export default function buildParse(options?: Options = {}): Parser {
  const { parserOptions, ...babelOptions } = options;
  const parserOpts = buildOptions(parserOptions, babelOptions);

  return {
    parse(src: string): ASTNode {
      return babel.parseSync(src, {
        parserOpts,
        ...babelOptions,
      });
    },
  };
}
<commit_msg>feat(babel): Support new topLevelAwait plugin in babel parser<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

const babel = require('@babel/core');
const path = require('path');

const TYPESCRIPT_EXTS = {
  '.ts': true,
  '.tsx': true,
};

function getDefaultPlugins(options: BabelOptions) {
  return [
    'jsx',
    TYPESCRIPT_EXTS[path.extname(options.filename || '')]
      ? 'typescript'
      : 'flow',
    'asyncGenerators',
    'bigInt',
    'classProperties',
    'classPrivateProperties',
    'classPrivateMethods',
    ['decorators', { decoratorsBeforeExport: false }],
    'doExpressions',
    'dynamicImport',
    'exportDefaultFrom',
    'exportNamespaceFrom',
    'functionBind',
    'functionSent',
    'importMeta',
    'logicalAssignment',
    'nullishCoalescingOperator',
    'numericSeparator',
    'objectRestSpread',
    'optionalCatchBinding',
    'optionalChaining',
    ['pipelineOperator', { proposal: 'minimal' }],
    'throwExpressions',
    'topLevelAwait',
  ];
}

export type Parser = {
  parse: (src: string) => ASTNode,
};

type ParserOptions = {
  plugins?: Array<string | [string, {}]>,
  tokens?: boolean,
};

type BabelOptions = {
  cwd?: string,
  filename?: string,
  envName?: string,
  babelrc?: boolean,
  root?: string,
  rootMode?: string,
  configFile?: string | false,
  babelrcRoots?: true | string | string[],
};

export type Options = BabelOptions & {
  parserOptions?: ParserOptions,
};

function buildOptions(
  parserOptions: ?ParserOptions,
  babelOptions: BabelOptions,
): ParserOptions {
  let parserOpts = {
    plugins: [],
  };

  if (parserOptions) {
    parserOpts = {
      ...parserOptions,
      plugins: parserOptions.plugins ? [...parserOptions.plugins] : [],
    };
  }

  const partialConfig = babel.loadPartialConfig(babelOptions);

  if (!partialConfig.hasFilesystemConfig() && parserOpts.plugins.length === 0) {
    parserOpts.plugins = getDefaultPlugins(babelOptions);
  }

  // Ensure we always have estree plugin enabled, if we add it a second time
  // here it does not matter
  parserOpts.plugins.push('estree');

  return parserOpts;
}

export default function buildParse(options?: Options = {}): Parser {
  const { parserOptions, ...babelOptions } = options;
  const parserOpts = buildOptions(parserOptions, babelOptions);

  return {
    parse(src: string): ASTNode {
      return babel.parseSync(src, {
        parserOpts,
        ...babelOptions,
      });
    },
  };
}
<|endoftext|>