sforkowany z mirror/soapbox
				
			Merge branch 'embeds-improvements' into 'develop'
Embeds improvements See merge request soapbox-pub/soapbox-fe!1752emoji-improvements
						commit
						0349a57f55
					
				| 
						 | 
				
			
			@ -0,0 +1,48 @@
 | 
			
		|||
import React, { useRef } from 'react';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
import { Button, HStack, Input } from './ui';
 | 
			
		||||
 | 
			
		||||
interface ICopyableInput {
 | 
			
		||||
  /** Text to be copied. */
 | 
			
		||||
  value: string,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/** An input with copy abilities. */
 | 
			
		||||
const CopyableInput: React.FC<ICopyableInput> = ({ value }) => {
 | 
			
		||||
  const input = useRef<HTMLInputElement>(null);
 | 
			
		||||
 | 
			
		||||
  const selectInput = () => {
 | 
			
		||||
    input.current?.select();
 | 
			
		||||
 | 
			
		||||
    if (navigator.clipboard) {
 | 
			
		||||
      navigator.clipboard.writeText(value);
 | 
			
		||||
    } else {
 | 
			
		||||
      document.execCommand('copy');
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <HStack alignItems='center'>
 | 
			
		||||
      <Input
 | 
			
		||||
        ref={input}
 | 
			
		||||
        type='text'
 | 
			
		||||
        value={value}
 | 
			
		||||
        className='rounded-r-none'
 | 
			
		||||
        outerClassName='flex-grow'
 | 
			
		||||
        onClick={selectInput}
 | 
			
		||||
        readOnly
 | 
			
		||||
      />
 | 
			
		||||
 | 
			
		||||
      <Button
 | 
			
		||||
        theme='primary'
 | 
			
		||||
        className='mt-1 h-full rounded-l-none rounded-r-lg'
 | 
			
		||||
        onClick={selectInput}
 | 
			
		||||
      >
 | 
			
		||||
        <FormattedMessage id='input.copy' defaultMessage='Copy' />
 | 
			
		||||
      </Button>
 | 
			
		||||
    </HStack>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default CopyableInput;
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1,4 @@
 | 
			
		|||
import classNames from 'classnames';
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import { Link } from 'react-router-dom';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -12,8 +13,8 @@ interface IButton {
 | 
			
		|||
  block?: boolean,
 | 
			
		||||
  /** Elements inside the <button> */
 | 
			
		||||
  children?: React.ReactNode,
 | 
			
		||||
  /** @deprecated unused */
 | 
			
		||||
  classNames?: string,
 | 
			
		||||
  /** Extra class names for the button. */
 | 
			
		||||
  className?: string,
 | 
			
		||||
  /** Prevent the button from being clicked. */
 | 
			
		||||
  disabled?: boolean,
 | 
			
		||||
  /** URL to an SVG icon to render inside the button. */
 | 
			
		||||
| 
						 | 
				
			
			@ -22,8 +23,6 @@ interface IButton {
 | 
			
		|||
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void,
 | 
			
		||||
  /** A predefined button size. */
 | 
			
		||||
  size?: ButtonSizes,
 | 
			
		||||
  /** @deprecated unused */
 | 
			
		||||
  style?: React.CSSProperties,
 | 
			
		||||
  /** Text inside the button. Takes precedence over `children`. */
 | 
			
		||||
  text?: React.ReactNode,
 | 
			
		||||
  /** Makes the button into a navlink, if provided. */
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +46,7 @@ const Button = React.forwardRef<HTMLButtonElement, IButton>((props, ref): JSX.El
 | 
			
		|||
    theme = 'secondary',
 | 
			
		||||
    to,
 | 
			
		||||
    type = 'button',
 | 
			
		||||
    className,
 | 
			
		||||
  } = props;
 | 
			
		||||
 | 
			
		||||
  const themeClass = useButtonStyles({
 | 
			
		||||
| 
						 | 
				
			
			@ -72,7 +72,7 @@ const Button = React.forwardRef<HTMLButtonElement, IButton>((props, ref): JSX.El
 | 
			
		|||
 | 
			
		||||
  const renderButton = () => (
 | 
			
		||||
    <button
 | 
			
		||||
      className={themeClass}
 | 
			
		||||
      className={classNames(themeClass, className)}
 | 
			
		||||
      disabled={disabled}
 | 
			
		||||
      onClick={handleClick}
 | 
			
		||||
      ref={ref}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -149,12 +149,6 @@ const SoapboxMount = () => {
 | 
			
		|||
        <Route path='/verify' component={AuthLayout} />
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      <Route
 | 
			
		||||
        path='/embed/:statusId'
 | 
			
		||||
        render={(props) => <EmbeddedStatus params={props.match.params} />}
 | 
			
		||||
      />
 | 
			
		||||
      <Redirect from='/@:username/:statusId/embed' to='/embed/:statusId' />
 | 
			
		||||
 | 
			
		||||
      <Route path='/reset-password' component={AuthLayout} />
 | 
			
		||||
      <Route path='/edit-password' component={AuthLayout} />
 | 
			
		||||
      <Route path='/invite/:token' component={AuthLayout} />
 | 
			
		||||
| 
						 | 
				
			
			@ -176,19 +170,27 @@ const SoapboxMount = () => {
 | 
			
		|||
    <ErrorBoundary>
 | 
			
		||||
      <BrowserRouter basename={BuildConfig.FE_SUBDIRECTORY}>
 | 
			
		||||
        <ScrollContext shouldUpdateScroll={shouldUpdateScroll}>
 | 
			
		||||
          <>
 | 
			
		||||
            {renderBody()}
 | 
			
		||||
          <Switch>
 | 
			
		||||
            <Route
 | 
			
		||||
              path='/embed/:statusId'
 | 
			
		||||
              render={(props) => <EmbeddedStatus params={props.match.params} />}
 | 
			
		||||
            />
 | 
			
		||||
            <Redirect from='/@:username/:statusId/embed' to='/embed/:statusId' />
 | 
			
		||||
 | 
			
		||||
            <BundleContainer fetchComponent={NotificationsContainer}>
 | 
			
		||||
              {(Component) => <Component />}
 | 
			
		||||
            </BundleContainer>
 | 
			
		||||
            <Route>
 | 
			
		||||
              {renderBody()}
 | 
			
		||||
 | 
			
		||||
            <BundleContainer fetchComponent={ModalContainer}>
 | 
			
		||||
              {Component => <Component />}
 | 
			
		||||
            </BundleContainer>
 | 
			
		||||
              <BundleContainer fetchComponent={NotificationsContainer}>
 | 
			
		||||
                {(Component) => <Component />}
 | 
			
		||||
              </BundleContainer>
 | 
			
		||||
 | 
			
		||||
            <GdprBanner />
 | 
			
		||||
          </>
 | 
			
		||||
              <BundleContainer fetchComponent={ModalContainer}>
 | 
			
		||||
                {Component => <Component />}
 | 
			
		||||
              </BundleContainer>
 | 
			
		||||
 | 
			
		||||
              <GdprBanner />
 | 
			
		||||
            </Route>
 | 
			
		||||
          </Switch>
 | 
			
		||||
        </ScrollContext>
 | 
			
		||||
      </BrowserRouter>
 | 
			
		||||
    </ErrorBoundary>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,8 @@ import React from 'react';
 | 
			
		|||
import { useDispatch } from 'react-redux';
 | 
			
		||||
 | 
			
		||||
import { openModal } from 'soapbox/actions/modals';
 | 
			
		||||
import CopyableInput from 'soapbox/components/copyable-input';
 | 
			
		||||
import { Text, Icon, Stack, HStack } from 'soapbox/components/ui';
 | 
			
		||||
import { CopyableInput } from 'soapbox/features/forms';
 | 
			
		||||
 | 
			
		||||
import { getExplorerUrl } from '../utils/block_explorer';
 | 
			
		||||
import { getTitle } from '../utils/coin_db';
 | 
			
		||||
| 
						 | 
				
			
			@ -57,9 +57,7 @@ const CryptoAddress: React.FC<ICryptoAddress> = (props): JSX.Element => {
 | 
			
		|||
        <Text>{note}</Text>
 | 
			
		||||
      )}
 | 
			
		||||
 | 
			
		||||
      <div className='crypto-address__address simple_form'>
 | 
			
		||||
        <CopyableInput value={address} />
 | 
			
		||||
      </div>
 | 
			
		||||
      <CopyableInput value={address} />
 | 
			
		||||
    </Stack>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,8 @@
 | 
			
		|||
import { QRCodeCanvas as QRCode } from 'qrcode.react';
 | 
			
		||||
import React from 'react';
 | 
			
		||||
 | 
			
		||||
import CopyableInput from 'soapbox/components/copyable-input';
 | 
			
		||||
import Icon from 'soapbox/components/icon';
 | 
			
		||||
import { CopyableInput } from 'soapbox/features/forms';
 | 
			
		||||
 | 
			
		||||
import { getExplorerUrl } from '../utils/block_explorer';
 | 
			
		||||
import { getTitle } from '../utils/coin_db';
 | 
			
		||||
| 
						 | 
				
			
			@ -38,9 +38,8 @@ const DetailedCryptoAddress: React.FC<IDetailedCryptoAddress> = ({ address, tick
 | 
			
		|||
      <div className='crypto-address__qrcode'>
 | 
			
		||||
        <QRCode value={address} />
 | 
			
		||||
      </div>
 | 
			
		||||
      <div className='crypto-address__address simple_form'>
 | 
			
		||||
        <CopyableInput value={address} />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <CopyableInput value={address} />
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,5 @@
 | 
			
		|||
import classNames from 'classnames';
 | 
			
		||||
import React, { useState, useRef } from 'react';
 | 
			
		||||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
import { v4 as uuidv4 } from 'uuid';
 | 
			
		||||
 | 
			
		||||
import { Text, Select } from '../../components/ui';
 | 
			
		||||
| 
						 | 
				
			
			@ -287,29 +286,3 @@ export const FileChooserLogo: React.FC<IFileChooserLogo> = props => (
 | 
			
		|||
FileChooserLogo.defaultProps = {
 | 
			
		||||
  accept: ['image/svg', 'image/png'],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface ICopyableInput {
 | 
			
		||||
  value: string,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const CopyableInput: React.FC<ICopyableInput> = ({ value }) => {
 | 
			
		||||
  const node = useRef<HTMLInputElement>(null);
 | 
			
		||||
 | 
			
		||||
  const handleCopyClick: React.MouseEventHandler = () => {
 | 
			
		||||
    if (!node.current) return;
 | 
			
		||||
 | 
			
		||||
    node.current.select();
 | 
			
		||||
    node.current.setSelectionRange(0, 99999);
 | 
			
		||||
 | 
			
		||||
    document.execCommand('copy');
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className='copyable-input'>
 | 
			
		||||
      <input ref={node} type='text' value={value} readOnly />
 | 
			
		||||
      <button className='p-2 text-white bg-primary-600' onClick={handleCopyClick}>
 | 
			
		||||
        <FormattedMessage id='forms.copy' defaultMessage='Copy' />
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,9 @@ import React, { useEffect } from 'react';
 | 
			
		|||
import { FormattedMessage } from 'react-intl';
 | 
			
		||||
 | 
			
		||||
import { closeModal } from 'soapbox/actions/modals';
 | 
			
		||||
import CopyableInput from 'soapbox/components/copyable-input';
 | 
			
		||||
import SafeEmbed from 'soapbox/components/safe-embed';
 | 
			
		||||
import { Modal, Stack, Text, Input, Divider } from 'soapbox/components/ui';
 | 
			
		||||
import { Modal, Stack, Text, Divider } from 'soapbox/components/ui';
 | 
			
		||||
import { useAppDispatch } from 'soapbox/hooks';
 | 
			
		||||
import useEmbed from 'soapbox/queries/embed';
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,10 +23,6 @@ const EmbedModal: React.FC<IEmbedModal> = ({ url, onError }) => {
 | 
			
		|||
    }
 | 
			
		||||
  }, [isError]);
 | 
			
		||||
 | 
			
		||||
  const handleInputClick: React.MouseEventHandler<HTMLInputElement> = (e) => {
 | 
			
		||||
    e.currentTarget.select();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const handleClose = () => {
 | 
			
		||||
    dispatch(closeModal('EMBED'));
 | 
			
		||||
  };
 | 
			
		||||
| 
						 | 
				
			
			@ -40,12 +37,7 @@ const EmbedModal: React.FC<IEmbedModal> = ({ url, onError }) => {
 | 
			
		|||
          <FormattedMessage id='embed.instructions' defaultMessage='Embed this post on your website by copying the code below.' />
 | 
			
		||||
        </Text>
 | 
			
		||||
 | 
			
		||||
        <Input
 | 
			
		||||
          type='text'
 | 
			
		||||
          readOnly
 | 
			
		||||
          value={embed?.html || ''}
 | 
			
		||||
          onClick={handleInputClick}
 | 
			
		||||
        />
 | 
			
		||||
        <CopyableInput value={embed?.html || ''} />
 | 
			
		||||
      </Stack>
 | 
			
		||||
 | 
			
		||||
      <div className='py-9'>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -242,7 +242,10 @@ const getInstanceFeatures = (instance: Instance) => {
 | 
			
		|||
     * Ability to embed posts on external sites.
 | 
			
		||||
     * @see GET /api/oembed
 | 
			
		||||
     */
 | 
			
		||||
    embeds: v.software === MASTODON,
 | 
			
		||||
    embeds: any([
 | 
			
		||||
      v.software === MASTODON,
 | 
			
		||||
      v.software === TRUTHSOCIAL,
 | 
			
		||||
    ]),
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Ability to add emoji reactions to a status.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue