mirror of https://github.com/tootsuite/mastodon
Merge 0d489fecba into 3c7f3b190c
commit
613b9c0f58
|
|
@ -414,7 +414,7 @@ export function initMediaEditModal(id) {
|
|||
|
||||
dispatch(openModal({
|
||||
modalType: 'FOCAL_POINT',
|
||||
modalProps: { id },
|
||||
modalProps: { mediaId: id },
|
||||
}));
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ interface BaseProps
|
|||
extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, 'children'> {
|
||||
block?: boolean;
|
||||
secondary?: boolean;
|
||||
compact?: boolean;
|
||||
dangerous?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -27,6 +28,7 @@ export const Button: React.FC<Props> = ({
|
|||
disabled,
|
||||
block,
|
||||
secondary,
|
||||
compact,
|
||||
dangerous,
|
||||
className,
|
||||
title,
|
||||
|
|
@ -47,6 +49,7 @@ export const Button: React.FC<Props> = ({
|
|||
<button
|
||||
className={classNames('button', className, {
|
||||
'button-secondary': secondary,
|
||||
'button--compact': compact,
|
||||
'button--block': block,
|
||||
'button--dangerous': dangerous,
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { useIntl, defineMessages } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { useIdentity } from '@/mastodon/identity_context';
|
||||
import { fetchRelationships, followAccount } from 'mastodon/actions/accounts';
|
||||
|
|
@ -20,7 +21,8 @@ const messages = defineMessages({
|
|||
|
||||
export const FollowButton: React.FC<{
|
||||
accountId?: string;
|
||||
}> = ({ accountId }) => {
|
||||
compact?: boolean;
|
||||
}> = ({ accountId, compact }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { signedIn } = useIdentity();
|
||||
|
|
@ -89,7 +91,9 @@ export const FollowButton: React.FC<{
|
|||
href='/settings/profile'
|
||||
target='_blank'
|
||||
rel='noopener'
|
||||
className='button button-secondary'
|
||||
className={classNames('button button-secondary', {
|
||||
'button--compact': compact,
|
||||
})}
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
|
|
@ -106,6 +110,7 @@ export const FollowButton: React.FC<{
|
|||
(account?.suspended || !!account?.moved))
|
||||
}
|
||||
secondary={following}
|
||||
compact={compact}
|
||||
className={following ? 'button--destructive' : undefined}
|
||||
>
|
||||
{label}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,10 @@ import { useCallback, useState } from 'react';
|
|||
|
||||
interface Props {
|
||||
src: string;
|
||||
key: string;
|
||||
alt?: string;
|
||||
lang?: string;
|
||||
width: number;
|
||||
height: number;
|
||||
width?: number;
|
||||
height?: number;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
|
|
@ -20,27 +19,22 @@ export const GIFV: React.FC<Props> = ({
|
|||
}) => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const handleLoadedData: React.ReactEventHandler<HTMLVideoElement> =
|
||||
useCallback(() => {
|
||||
setLoading(false);
|
||||
}, [setLoading]);
|
||||
const handleLoadedData = useCallback(() => {
|
||||
setLoading(false);
|
||||
}, [setLoading]);
|
||||
|
||||
const handleClick: React.MouseEventHandler = useCallback(
|
||||
(e) => {
|
||||
if (onClick) {
|
||||
e.stopPropagation();
|
||||
onClick();
|
||||
}
|
||||
const handleClick = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onClick?.();
|
||||
},
|
||||
[onClick],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='gifv' style={{ position: 'relative' }}>
|
||||
<div className='gifv'>
|
||||
{loading && (
|
||||
<canvas
|
||||
width={width}
|
||||
height={height}
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
aria-label={alt}
|
||||
|
|
@ -57,13 +51,14 @@ export const GIFV: React.FC<Props> = ({
|
|||
aria-label={alt}
|
||||
title={alt}
|
||||
lang={lang}
|
||||
width={width}
|
||||
height={height}
|
||||
muted
|
||||
loop
|
||||
autoPlay
|
||||
playsInline
|
||||
onClick={handleClick}
|
||||
onLoadedData={handleLoadedData}
|
||||
style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,244 @@
|
|||
import { useState, useCallback, useRef } from 'react';
|
||||
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
|
||||
|
||||
// eslint-disable-next-line import/extensions
|
||||
import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js';
|
||||
|
||||
import { changeUploadCompose, uploadThumbnail } from 'mastodon/actions/compose';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
import Audio from 'mastodon/features/audio';
|
||||
import Video from 'mastodon/features/video';
|
||||
import { GIFV } from 'mastodon/components/gifv';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholderVisual: {
|
||||
id: '',
|
||||
defaultMessage: 'Describe this for people with visual impairments…',
|
||||
},
|
||||
placeholderHearing: {
|
||||
id: '',
|
||||
defaultMessage: 'Describe this for people with hearing impairments…',
|
||||
},
|
||||
});
|
||||
|
||||
const UploadButton: React.FC<{
|
||||
children: React.ReactNode;
|
||||
onSelectFile: (arg0?: File) => void;
|
||||
}> = ({ children, onSelectFile }) => {
|
||||
const fileRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleClick = useCallback(() => {
|
||||
fileRef.current?.click();
|
||||
}, []);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
|
||||
if (files && files.length > 0) {
|
||||
onSelectFile(files[0]);
|
||||
}
|
||||
},
|
||||
[onSelectFile],
|
||||
);
|
||||
|
||||
return (
|
||||
<label>
|
||||
<Button onClick={handleClick}>{children}</Button>
|
||||
|
||||
<input
|
||||
id='upload-modal__thumbnail'
|
||||
ref={fileRef}
|
||||
type='file'
|
||||
accept='image/png,image/jpeg'
|
||||
onChange={handleChange}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
const Preview: React.FC<{ mediaId: string }> = ({ mediaId }) => {
|
||||
const media = useAppSelector((state) =>
|
||||
state.compose.get('media_attachments').find((x) => x.get('id') === mediaId),
|
||||
);
|
||||
const account = useAppSelector((state) =>
|
||||
me ? state.accounts.get(me) : undefined,
|
||||
);
|
||||
|
||||
if (media.get('type') === 'image') {
|
||||
return <img src={media.get('url')} alt='' />;
|
||||
} else if (media.get('type') === 'gifv') {
|
||||
return <GIFV src={media.get('url')} />;
|
||||
} else if (media.get('type') === 'video') {
|
||||
return (
|
||||
<Video
|
||||
preview={media.get('preview_url')}
|
||||
frameRate={media.getIn(['meta', 'original', 'frame_rate'])}
|
||||
blurhash={media.get('blurhash')}
|
||||
src={media.get('url')}
|
||||
detailed
|
||||
inline
|
||||
editable
|
||||
/>
|
||||
);
|
||||
} else if (media.get('type') === 'audio') {
|
||||
return (
|
||||
<Audio
|
||||
src={media.get('url')}
|
||||
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
||||
poster={media.get('preview_url') ?? account?.avatar_static}
|
||||
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={media.getIn(['meta', 'colors', 'accent'])}
|
||||
editable
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const AltTextModal: React.FC<{
|
||||
mediaId: string;
|
||||
onClose: () => void;
|
||||
}> = ({ mediaId, onClose }) => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const media = useAppSelector((state) =>
|
||||
state.compose.get('media_attachments').find((x) => x.get('id') === mediaId),
|
||||
);
|
||||
const lang = useAppSelector((state) => state.compose.get('lang'));
|
||||
const [description, setDescription] = useState(
|
||||
media.get('description') ?? '',
|
||||
);
|
||||
const [isDetecting, setIsDetecting] = useState(false);
|
||||
const type = media.get('type') as string;
|
||||
|
||||
const handleDescriptionChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setDescription(e.target.value);
|
||||
},
|
||||
[setDescription],
|
||||
);
|
||||
|
||||
const handleThumbnailChange = useCallback(
|
||||
(file: File) => {
|
||||
dispatch(uploadThumbnail(mediaId, file));
|
||||
},
|
||||
[dispatch, mediaId],
|
||||
);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
dispatch(changeUploadCompose(mediaId, { description }));
|
||||
onClose();
|
||||
}, [dispatch, mediaId, onClose, description]);
|
||||
|
||||
const handleDetectClick = useCallback(() => {
|
||||
setIsDetecting(true);
|
||||
|
||||
void fetchTesseract().then(async ({ createWorker }) => {
|
||||
const worker = await createWorker('eng', 1, {
|
||||
workerPath: tesseractWorkerPath,
|
||||
corePath: tesseractCorePath,
|
||||
langPath: `${assetHost}/ocr/lang-data`,
|
||||
cacheMethod: 'write',
|
||||
logger: (m) => console.log(m),
|
||||
});
|
||||
|
||||
const image = URL.createObjectURL(media.get('file'));
|
||||
const result = await worker.recognize(image);
|
||||
|
||||
setDescription(result.data.text);
|
||||
setIsDetecting(false);
|
||||
|
||||
return '';
|
||||
});
|
||||
}, [setDescription, setIsDetecting, media]);
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal dialog-modal'>
|
||||
<div className='dialog-modal__header'>
|
||||
<Button onClick={handleSubmit}>
|
||||
<FormattedMessage id='' defaultMessage='Done' />
|
||||
</Button>
|
||||
|
||||
<span className='dialog-modal__header__title'>
|
||||
<FormattedMessage id='' defaultMessage='Add alt text' />
|
||||
</span>
|
||||
|
||||
<Button secondary onClick={onClose}>
|
||||
<FormattedMessage id='' defaultMessage='Cancel' />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className='dialog-modal__content'>
|
||||
<div className='dialog-modal__content__preview'>
|
||||
<Preview mediaId={mediaId} />
|
||||
|
||||
{(type === 'audio' || type === 'video') && (
|
||||
<UploadButton onSelectFile={handleThumbnailChange}>
|
||||
<FormattedMessage id='' defaultMessage='Change thumbnail' />
|
||||
</UploadButton>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<form
|
||||
className='dialog-modal__content__form simple_form'
|
||||
onSubmit={handleSubmit}
|
||||
>
|
||||
<div className='input'>
|
||||
<div className='label_input'>
|
||||
<Textarea
|
||||
id='description'
|
||||
value={isDetecting ? ' ' : description}
|
||||
onChange={handleDescriptionChange}
|
||||
lang={lang}
|
||||
placeholder={intl.formatMessage(
|
||||
type === 'audio'
|
||||
? messages.placeholderHearing
|
||||
: messages.placeholderVisual,
|
||||
)}
|
||||
minRows={3}
|
||||
disabled={isDetecting}
|
||||
/>
|
||||
|
||||
{isDetecting && (
|
||||
<div className='label_input__loading-indicator'>
|
||||
<Skeleton width='100%' />
|
||||
<Skeleton width='100%' />
|
||||
<Skeleton width='61%' />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className='input__toolbar'>
|
||||
<button
|
||||
className='link-button'
|
||||
onClick={handleDetectClick}
|
||||
disabled={type !== 'image' || isDetecting}
|
||||
>
|
||||
<FormattedMessage id='' defaultMessage='Add text from image' />
|
||||
</button>
|
||||
|
||||
<CharacterCounter
|
||||
max={1500}
|
||||
text={isDetecting ? '' : description}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -581,10 +581,14 @@ class Audio extends PureComponent {
|
|||
</div>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{!editable && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' icon={VisibilityOffIcon} /></button>}
|
||||
<a title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} className='video-player__download__icon player-button' href={this.props.src} download>
|
||||
<Icon id={'download'} icon={DownloadIcon} />
|
||||
</a>
|
||||
{!editable && (
|
||||
<>
|
||||
<button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} className='player-button' onClick={this.toggleReveal}><Icon id='eye-slash' icon={VisibilityOffIcon} /></button>
|
||||
<a title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)} className='video-player__download__icon player-button' href={this.props.src} download>
|
||||
<Icon id='download' icon={DownloadIcon} />
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { length } from 'stringz';
|
||||
|
||||
export const CharacterCounter = ({ text, max }) => {
|
||||
const diff = max - length(text);
|
||||
|
||||
if (diff < 0) {
|
||||
return <span className='character-counter character-counter--over'>{diff}</span>;
|
||||
}
|
||||
|
||||
return <span className='character-counter'>{diff}</span>;
|
||||
};
|
||||
|
||||
CharacterCounter.propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
max: PropTypes.number.isRequired,
|
||||
};
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { length } from 'stringz';
|
||||
|
||||
export const CharacterCounter: React.FC<{
|
||||
text: string;
|
||||
max: number;
|
||||
}> = ({ text, max }) => {
|
||||
const diff = max - length(text);
|
||||
|
||||
if (diff < 0) {
|
||||
return (
|
||||
<span className='character-counter character-counter--over'>{diff}</span>
|
||||
);
|
||||
}
|
||||
|
||||
return <span className='character-counter'>{diff}</span>;
|
||||
};
|
||||
|
|
@ -301,6 +301,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
<div className='compose-form__submit'>
|
||||
<Button
|
||||
type='submit'
|
||||
compact
|
||||
text={intl.formatMessage(this.props.isEditing ? messages.saveChanges : (this.props.isInReply ? messages.reply : messages.publish))}
|
||||
disabled={!this.canSubmit()}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -65,7 +65,9 @@ export const NotificationFollow: React.FC<{
|
|||
const account = notification.sampleAccountIds[0];
|
||||
|
||||
if (account) {
|
||||
actions = <FollowButton accountId={notification.sampleAccountIds[0]} />;
|
||||
actions = (
|
||||
<FollowButton compact accountId={notification.sampleAccountIds[0]} />
|
||||
);
|
||||
additionalContent = <FollowerCount accountId={account} />;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,438 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
import { length } from 'stringz';
|
||||
// eslint-disable-next-line import/extensions
|
||||
import tesseractWorkerPath from 'tesseract.js/dist/worker.min.js';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import tesseractCorePath from 'tesseract.js-core/tesseract-core.wasm.js';
|
||||
|
||||
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { GIFV } from 'mastodon/components/gifv';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import Audio from 'mastodon/features/audio';
|
||||
import { CharacterCounter } from 'mastodon/features/compose/components/character_counter';
|
||||
import { UploadProgress } from 'mastodon/features/compose/components/upload_progress';
|
||||
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { assetHost } from 'mastodon/utils/config';
|
||||
|
||||
import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose';
|
||||
import Video, { getPointerPosition } from '../../video';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
apply: { id: 'upload_modal.apply', defaultMessage: 'Apply' },
|
||||
applying: { id: 'upload_modal.applying', defaultMessage: 'Applying…' },
|
||||
placeholder: { id: 'upload_modal.description_placeholder', defaultMessage: 'A quick brown fox jumps over the lazy dog' },
|
||||
chooseImage: { id: 'upload_modal.choose_image', defaultMessage: 'Choose image' },
|
||||
discardMessage: { id: 'confirmations.discard_edit_media.message', defaultMessage: 'You have unsaved changes to the media description or preview, discard them anyway?' },
|
||||
discardConfirm: { id: 'confirmations.discard_edit_media.confirm', defaultMessage: 'Discard' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
|
||||
account: state.getIn(['accounts', me]),
|
||||
isUploadingThumbnail: state.getIn(['compose', 'isUploadingThumbnail']),
|
||||
description: state.getIn(['compose', 'media_modal', 'description']),
|
||||
lang: state.getIn(['compose', 'language']),
|
||||
focusX: state.getIn(['compose', 'media_modal', 'focusX']),
|
||||
focusY: state.getIn(['compose', 'media_modal', 'focusY']),
|
||||
dirty: state.getIn(['compose', 'media_modal', 'dirty']),
|
||||
is_changing_upload: state.getIn(['compose', 'is_changing_upload']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { id }) => ({
|
||||
|
||||
onSave: (description, x, y) => {
|
||||
dispatch(changeUploadCompose(id, { description, focus: `${x.toFixed(2)},${y.toFixed(2)}` }));
|
||||
},
|
||||
|
||||
onChangeDescription: (description) => {
|
||||
dispatch(onChangeMediaDescription(description));
|
||||
},
|
||||
|
||||
onChangeFocus: (focusX, focusY) => {
|
||||
dispatch(onChangeMediaFocus(focusX, focusY));
|
||||
},
|
||||
|
||||
onSelectThumbnail: files => {
|
||||
dispatch(uploadThumbnail(id, files[0]));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
|
||||
.replace(/\n/g, ' ')
|
||||
.replace(/\*\*\*\*\*\*/g, '\n\n');
|
||||
|
||||
class ImageLoader extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
src: PropTypes.string.isRequired,
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
};
|
||||
|
||||
state = {
|
||||
loading: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const image = new Image();
|
||||
image.addEventListener('load', () => this.setState({ loading: false }));
|
||||
image.src = this.props.src;
|
||||
}
|
||||
|
||||
render () {
|
||||
const { loading } = this.state;
|
||||
|
||||
if (loading) {
|
||||
return <canvas width={this.props.width} height={this.props.height} />;
|
||||
} else {
|
||||
return <img {...this.props} alt='' />;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class FocalPointModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.map.isRequired,
|
||||
account: ImmutablePropTypes.record.isRequired,
|
||||
isUploadingThumbnail: PropTypes.bool,
|
||||
onSave: PropTypes.func.isRequired,
|
||||
onChangeDescription: PropTypes.func.isRequired,
|
||||
onChangeFocus: PropTypes.func.isRequired,
|
||||
onSelectThumbnail: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
dragging: false,
|
||||
dirty: false,
|
||||
progress: 0,
|
||||
loading: true,
|
||||
ocrStatus: '',
|
||||
};
|
||||
|
||||
componentWillUnmount () {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||
}
|
||||
|
||||
handleMouseDown = e => {
|
||||
document.addEventListener('mousemove', this.handleMouseMove);
|
||||
document.addEventListener('mouseup', this.handleMouseUp);
|
||||
|
||||
this.updatePosition(e);
|
||||
this.setState({ dragging: true });
|
||||
};
|
||||
|
||||
handleTouchStart = e => {
|
||||
document.addEventListener('touchmove', this.handleMouseMove);
|
||||
document.addEventListener('touchend', this.handleTouchEnd);
|
||||
|
||||
this.updatePosition(e);
|
||||
this.setState({ dragging: true });
|
||||
};
|
||||
|
||||
handleMouseMove = e => {
|
||||
this.updatePosition(e);
|
||||
};
|
||||
|
||||
handleMouseUp = () => {
|
||||
document.removeEventListener('mousemove', this.handleMouseMove);
|
||||
document.removeEventListener('mouseup', this.handleMouseUp);
|
||||
|
||||
this.setState({ dragging: false });
|
||||
};
|
||||
|
||||
handleTouchEnd = () => {
|
||||
document.removeEventListener('touchmove', this.handleMouseMove);
|
||||
document.removeEventListener('touchend', this.handleTouchEnd);
|
||||
|
||||
this.setState({ dragging: false });
|
||||
};
|
||||
|
||||
updatePosition = e => {
|
||||
const { x, y } = getPointerPosition(this.node, e);
|
||||
const focusX = (x - .5) * 2;
|
||||
const focusY = (y - .5) * -2;
|
||||
|
||||
this.props.onChangeFocus(focusX, focusY);
|
||||
};
|
||||
|
||||
handleChange = e => {
|
||||
this.props.onChangeDescription(e.target.value);
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
this.props.onChangeDescription(e.target.value);
|
||||
this.handleSubmit(e);
|
||||
}
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.props.onSave(this.props.description, this.props.focusX, this.props.focusY);
|
||||
};
|
||||
|
||||
getCloseConfirmationMessage = () => {
|
||||
const { intl, dirty } = this.props;
|
||||
|
||||
if (dirty) {
|
||||
return {
|
||||
message: intl.formatMessage(messages.discardMessage),
|
||||
confirm: intl.formatMessage(messages.discardConfirm),
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
setRef = c => {
|
||||
this.node = c;
|
||||
};
|
||||
|
||||
handleTextDetection = () => {
|
||||
this._detectText();
|
||||
};
|
||||
|
||||
_detectText = (refreshCache = false) => {
|
||||
const { media } = this.props;
|
||||
|
||||
this.setState({ detecting: true });
|
||||
|
||||
fetchTesseract().then(({ createWorker }) => {
|
||||
const worker = createWorker({
|
||||
workerPath: tesseractWorkerPath,
|
||||
corePath: tesseractCorePath,
|
||||
langPath: `${assetHost}/ocr/lang-data`,
|
||||
logger: ({ status, progress }) => {
|
||||
if (status === 'recognizing text') {
|
||||
this.setState({ ocrStatus: 'detecting', progress });
|
||||
} else {
|
||||
this.setState({ ocrStatus: 'preparing', progress });
|
||||
}
|
||||
},
|
||||
cacheMethod: refreshCache ? 'refresh' : 'write',
|
||||
});
|
||||
|
||||
let media_url = media.get('url');
|
||||
|
||||
if (window.URL && URL.createObjectURL) {
|
||||
try {
|
||||
media_url = URL.createObjectURL(media.get('file'));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
return (async () => {
|
||||
await worker.load();
|
||||
await worker.loadLanguage('eng');
|
||||
await worker.initialize('eng');
|
||||
const { data: { text } } = await worker.recognize(media_url);
|
||||
this.setState({ detecting: false });
|
||||
this.props.onChangeDescription(removeExtraLineBreaks(text));
|
||||
await worker.terminate();
|
||||
})().catch((e) => {
|
||||
if (refreshCache) {
|
||||
throw e;
|
||||
} else {
|
||||
this._detectText(true);
|
||||
}
|
||||
});
|
||||
}).catch((e) => {
|
||||
console.error(e);
|
||||
this.setState({ detecting: false });
|
||||
});
|
||||
};
|
||||
|
||||
handleThumbnailChange = e => {
|
||||
if (e.target.files.length > 0) {
|
||||
this.props.onSelectThumbnail(e.target.files);
|
||||
}
|
||||
};
|
||||
|
||||
setFileInputRef = c => {
|
||||
this.fileInput = c;
|
||||
};
|
||||
|
||||
handleFileInputClick = () => {
|
||||
this.fileInput.click();
|
||||
};
|
||||
|
||||
render () {
|
||||
const { media, intl, account, onClose, isUploadingThumbnail, description, lang, focusX, focusY, dirty, is_changing_upload } = this.props;
|
||||
const { dragging, detecting, progress, ocrStatus } = this.state;
|
||||
const x = (focusX / 2) + .5;
|
||||
const y = (focusY / -2) + .5;
|
||||
|
||||
const width = media.getIn(['meta', 'original', 'width']) || null;
|
||||
const height = media.getIn(['meta', 'original', 'height']) || null;
|
||||
const focals = ['image', 'gifv'].includes(media.get('type'));
|
||||
const thumbnailable = ['audio', 'video'].includes(media.get('type'));
|
||||
|
||||
const previewRatio = 16/9;
|
||||
const previewWidth = 200;
|
||||
const previewHeight = previewWidth / previewRatio;
|
||||
|
||||
let descriptionLabel = null;
|
||||
|
||||
if (media.get('type') === 'audio') {
|
||||
descriptionLabel = <FormattedMessage id='upload_form.audio_description' defaultMessage='Describe for people who are hard of hearing' />;
|
||||
} else if (media.get('type') === 'video') {
|
||||
descriptionLabel = <FormattedMessage id='upload_form.video_description' defaultMessage='Describe for people who are deaf, hard of hearing, blind or have low vision' />;
|
||||
} else {
|
||||
descriptionLabel = <FormattedMessage id='upload_form.description' defaultMessage='Describe for people who are blind or have low vision' />;
|
||||
}
|
||||
|
||||
let ocrMessage = '';
|
||||
if (ocrStatus === 'detecting') {
|
||||
ocrMessage = <FormattedMessage id='upload_modal.analyzing_picture' defaultMessage='Analyzing picture…' />;
|
||||
} else {
|
||||
ocrMessage = <FormattedMessage id='upload_modal.preparing_ocr' defaultMessage='Preparing OCR…' />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='modal-root__modal report-modal' style={{ maxWidth: 960 }}>
|
||||
<div className='report-modal__target'>
|
||||
<IconButton className='report-modal__close' title={intl.formatMessage(messages.close)} icon='times' iconComponent={CloseIcon} onClick={onClose} size={20} />
|
||||
<FormattedMessage id='upload_modal.edit_media' defaultMessage='Edit media' />
|
||||
</div>
|
||||
|
||||
<div className='report-modal__container'>
|
||||
<form className='report-modal__comment' onSubmit={this.handleSubmit} >
|
||||
{focals && <p><FormattedMessage id='upload_modal.hint' defaultMessage='Click or drag the circle on the preview to choose the focal point which will always be in view on all thumbnails.' /></p>}
|
||||
|
||||
{thumbnailable && (
|
||||
<>
|
||||
<label className='setting-text-label' htmlFor='upload-modal__thumbnail'><FormattedMessage id='upload_form.thumbnail' defaultMessage='Change thumbnail' /></label>
|
||||
|
||||
<Button disabled={isUploadingThumbnail || !media.get('unattached')} text={intl.formatMessage(messages.chooseImage)} onClick={this.handleFileInputClick} />
|
||||
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.chooseImage)}</span>
|
||||
|
||||
<input
|
||||
id='upload-modal__thumbnail'
|
||||
ref={this.setFileInputRef}
|
||||
type='file'
|
||||
accept='image/png,image/jpeg'
|
||||
onChange={this.handleThumbnailChange}
|
||||
style={{ display: 'none' }}
|
||||
disabled={isUploadingThumbnail || is_changing_upload}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<hr className='setting-divider' />
|
||||
</>
|
||||
)}
|
||||
|
||||
<label className='setting-text-label' htmlFor='upload-modal__description'>
|
||||
{descriptionLabel}
|
||||
</label>
|
||||
|
||||
<div className='setting-text__wrapper'>
|
||||
<Textarea
|
||||
id='upload-modal__description'
|
||||
className='setting-text light'
|
||||
value={detecting ? '…' : description}
|
||||
lang={lang}
|
||||
onChange={this.handleChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
disabled={detecting || is_changing_upload}
|
||||
autoFocus
|
||||
/>
|
||||
|
||||
<div className='setting-text__modifiers'>
|
||||
<UploadProgress progress={progress * 100} active={detecting} icon='file-text-o' message={ocrMessage} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='setting-text__toolbar'>
|
||||
<button
|
||||
type='button'
|
||||
disabled={detecting || media.get('type') !== 'image' || is_changing_upload}
|
||||
className='link-button'
|
||||
onClick={this.handleTextDetection}
|
||||
>
|
||||
<FormattedMessage id='upload_modal.detect_text' defaultMessage='Detect text from picture' />
|
||||
</button>
|
||||
<CharacterCounter max={1500} text={detecting ? '' : description} />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type='submit'
|
||||
disabled={!dirty || detecting || isUploadingThumbnail || length(description) > 1500 || is_changing_upload}
|
||||
text={intl.formatMessage(is_changing_upload ? messages.applying : messages.apply)}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<div className='focal-point-modal__content'>
|
||||
{focals && (
|
||||
<div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
|
||||
{media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />}
|
||||
{media.get('type') === 'gifv' && <GIFV src={media.get('url')} key={media.get('url')} width={width} height={height} />}
|
||||
|
||||
<div className='focal-point__preview'>
|
||||
<strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>
|
||||
<div style={{ width: previewWidth, height: previewHeight, backgroundImage: `url(${media.get('preview_url')})`, backgroundSize: 'cover', backgroundPosition: `${x * 100}% ${y * 100}%` }} />
|
||||
</div>
|
||||
|
||||
<div className='focal-point__reticle' style={{ top: `${y * 100}%`, left: `${x * 100}%` }} />
|
||||
<div className='focal-point__overlay' />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{media.get('type') === 'video' && (
|
||||
<Video
|
||||
preview={media.get('preview_url')}
|
||||
frameRate={media.getIn(['meta', 'original', 'frame_rate'])}
|
||||
blurhash={media.get('blurhash')}
|
||||
src={media.get('url')}
|
||||
detailed
|
||||
inline
|
||||
editable
|
||||
/>
|
||||
)}
|
||||
|
||||
{media.get('type') === 'audio' && (
|
||||
<Audio
|
||||
src={media.get('url')}
|
||||
duration={media.getIn(['meta', 'original', 'duration'], 0)}
|
||||
height={150}
|
||||
poster={media.get('preview_url') || account.get('avatar_static')}
|
||||
backgroundColor={media.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={media.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={media.getIn(['meta', 'colors', 'accent'])}
|
||||
editable
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, null, {
|
||||
forwardRef: true,
|
||||
})(injectIntl(FocalPointModal, { forwardRef: true }));
|
||||
|
|
@ -37,7 +37,7 @@ import {
|
|||
ConfirmLogOutModal,
|
||||
ConfirmFollowToListModal,
|
||||
} from './confirmation_modals';
|
||||
import FocalPointModal from './focal_point_modal';
|
||||
import { AltTextModal } from 'mastodon/features/alt_text_modal';
|
||||
import ImageModal from './image_modal';
|
||||
import MediaModal from './media_modal';
|
||||
import { ModalPlaceholder } from './modal_placeholder';
|
||||
|
|
@ -64,7 +64,7 @@ export const MODAL_COMPONENTS = {
|
|||
'REPORT': ReportModal,
|
||||
'ACTIONS': () => Promise.resolve({ default: ActionsModal }),
|
||||
'EMBED': EmbedModal,
|
||||
'FOCAL_POINT': () => Promise.resolve({ default: FocalPointModal }),
|
||||
'FOCAL_POINT': () => Promise.resolve({ default: AltTextModal }),
|
||||
'LIST_ADDER': ListAdder,
|
||||
'COMPARE_HISTORY': CompareHistoryModal,
|
||||
'FILTER': FilterModal,
|
||||
|
|
|
|||
|
|
@ -85,6 +85,14 @@
|
|||
outline: $ui-button-icon-focus-outline;
|
||||
}
|
||||
|
||||
&--compact {
|
||||
font-size: 14px;
|
||||
line-height: normal;
|
||||
font-weight: 700;
|
||||
padding: 5px 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&--dangerous {
|
||||
background-color: var(--error-background-color);
|
||||
color: var(--on-error-color);
|
||||
|
|
@ -3724,58 +3732,6 @@ $ui-header-logo-wordmark-width: 99px;
|
|||
}
|
||||
}
|
||||
|
||||
.setting-text {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
color: $primary-text-color;
|
||||
background: $ui-base-color;
|
||||
padding: 7px 10px;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--background-border-color);
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
background: $ui-base-color;
|
||||
border: 1px solid var(--background-border-color);
|
||||
margin-bottom: 10px;
|
||||
border-radius: 4px;
|
||||
|
||||
.setting-text {
|
||||
border: 0;
|
||||
margin-bottom: 0;
|
||||
border-radius: 0;
|
||||
|
||||
&:focus {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__modifiers {
|
||||
color: $inverted-text-color;
|
||||
font-family: inherit;
|
||||
font-size: 14px;
|
||||
background: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&__toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@media screen and (width <= 600px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.status-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
|
@ -6094,6 +6050,28 @@ a.status-card {
|
|||
gap: 16px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
&__preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
background: #000;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
}
|
||||
|
||||
img,
|
||||
.gifv video,
|
||||
.video-player,
|
||||
.audio-player {
|
||||
max-width: 360px;
|
||||
max-height: 90vh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.copy-paste-text {
|
||||
|
|
@ -6440,62 +6418,6 @@ a.status-card {
|
|||
margin-bottom: 29px;
|
||||
}
|
||||
|
||||
.report-modal__comment {
|
||||
padding: 20px;
|
||||
border-inline-end: 1px solid var(--background-border-color);
|
||||
max-width: 320px;
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.setting-text-label {
|
||||
display: block;
|
||||
color: $secondary-text-color;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.setting-text {
|
||||
width: 100%;
|
||||
resize: none;
|
||||
min-height: 100px;
|
||||
max-height: 50vh;
|
||||
border: 0;
|
||||
|
||||
@media screen and (height <= 600px) {
|
||||
max-height: 20vh;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-columns-breakpoint) {
|
||||
max-height: 20vh;
|
||||
}
|
||||
}
|
||||
|
||||
.setting-toggle {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
&__label {
|
||||
color: $inverted-text-color;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (width <= 480px) {
|
||||
padding: 10px;
|
||||
max-width: 100%;
|
||||
order: 2;
|
||||
|
||||
.setting-toggle {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions-modal {
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
|
|
@ -7367,6 +7289,14 @@ a.status-card {
|
|||
}
|
||||
|
||||
.gifv {
|
||||
position: relative;
|
||||
|
||||
canvas {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
video {
|
||||
max-width: 100vw;
|
||||
max-height: 80vh;
|
||||
|
|
@ -10454,12 +10384,7 @@ noscript {
|
|||
.compose-form__actions {
|
||||
.button {
|
||||
display: block; // Otherwise text-ellipsis doesn't work
|
||||
font-size: 14px;
|
||||
line-height: normal;
|
||||
font-weight: 700;
|
||||
flex: 1 1 auto;
|
||||
padding: 5px 12px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -76,6 +76,18 @@ code {
|
|||
margin-bottom: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&__toolbar {
|
||||
margin-top: 16px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
&.hidden {
|
||||
margin: 0;
|
||||
}
|
||||
|
|
@ -552,9 +564,7 @@ code {
|
|||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
button,
|
||||
.button,
|
||||
.block-button {
|
||||
button:not(.button):not(.link-button) {
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
|
|
@ -629,6 +639,18 @@ code {
|
|||
}
|
||||
|
||||
.label_input {
|
||||
position: relative;
|
||||
|
||||
&__loading-indicator {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
border: 1px solid transparent;
|
||||
padding: 10px 16px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
.simple_form
|
||||
%p.hint= t('admin.relays.description_html')
|
||||
= link_to @relays.empty? ? t('admin.relays.setup') : t('admin.relays.add_new'), new_admin_relay_path, class: 'block-button'
|
||||
= link_to @relays.empty? ? t('admin.relays.setup') : t('admin.relays.add_new'), new_admin_relay_path, class: 'button button--block'
|
||||
|
||||
- unless @relays.empty?
|
||||
%hr.spacer
|
||||
|
|
|
|||
|
|
@ -6,4 +6,4 @@
|
|||
|
||||
%hr.spacer/
|
||||
|
||||
= link_to t('otp_authentication.setup'), settings_otp_authentication_path, data: { method: :post }, class: 'block-button'
|
||||
= link_to t('otp_authentication.setup'), settings_otp_authentication_path, data: { method: :post }, class: 'button button--block'
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@
|
|||
%hr.spacer/
|
||||
|
||||
.simple_form
|
||||
= link_to t('webauthn_credentials.add'), new_settings_webauthn_credential_path, class: 'block-button'
|
||||
= link_to t('webauthn_credentials.add'), new_settings_webauthn_credential_path, class: 'button button--block'
|
||||
|
|
|
|||
|
|
@ -38,4 +38,4 @@
|
|||
%hr.spacer/
|
||||
|
||||
.simple_form
|
||||
= link_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, data: { method: :post }, class: 'block-button'
|
||||
= link_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, data: { method: :post }, class: 'button button--block'
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@
|
|||
"stringz": "^2.1.0",
|
||||
"substring-trie": "^1.0.2",
|
||||
"terser-webpack-plugin": "^4.2.3",
|
||||
"tesseract.js": "^2.1.5",
|
||||
"tesseract.js": "^5.1.1",
|
||||
"tiny-queue": "^0.2.1",
|
||||
"twitter-text": "3.1.0",
|
||||
"use-debounce": "^10.0.0",
|
||||
|
|
|
|||
117
yarn.lock
117
yarn.lock
|
|
@ -3018,7 +3018,7 @@ __metadata:
|
|||
stylelint-config-standard-scss: "npm:^14.0.0"
|
||||
substring-trie: "npm:^1.0.2"
|
||||
terser-webpack-plugin: "npm:^4.2.3"
|
||||
tesseract.js: "npm:^2.1.5"
|
||||
tesseract.js: "npm:^5.1.1"
|
||||
tiny-queue: "npm:^0.2.1"
|
||||
twitter-text: "npm:3.1.0"
|
||||
typescript: "npm:^5.0.4"
|
||||
|
|
@ -5642,13 +5642,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"blueimp-load-image@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "blueimp-load-image@npm:3.0.0"
|
||||
checksum: 10c0/e860da4113afd8e58bc026fb17240007e15dc155287a70fb57b3048fc8f0aa5f7dbd052efed8bff19d1208eeab4d058dc6788684a721c50ccd08b68d836a8d18
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"blurhash@npm:^2.0.5":
|
||||
version: 2.0.5
|
||||
resolution: "blurhash@npm:2.0.5"
|
||||
|
|
@ -6386,13 +6379,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"colors@npm:^1.4.0":
|
||||
version: 1.4.0
|
||||
resolution: "colors@npm:1.4.0"
|
||||
checksum: 10c0/9af357c019da3c5a098a301cf64e3799d27549d8f185d86f79af23069e4f4303110d115da98483519331f6fb71c8568d5688fa1c6523600044fd4a54e97c4efb
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"combined-stream@npm:^1.0.8":
|
||||
version: 1.0.8
|
||||
resolution: "combined-stream@npm:1.0.8"
|
||||
|
|
@ -8727,13 +8713,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-type@npm:^12.4.1":
|
||||
version: 12.4.2
|
||||
resolution: "file-type@npm:12.4.2"
|
||||
checksum: 10c0/26a307262a2a0b41ea83136550fbe83d8b502d080778b6577e0336fbfe9e919e1f871a286a6eb59f668425f60ebb19402fcb6c0443af58446d33c63362554e1d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"file-uri-to-path@npm:1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "file-uri-to-path@npm:1.0.0"
|
||||
|
|
@ -9765,10 +9744,10 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"idb-keyval@npm:^3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "idb-keyval@npm:3.2.0"
|
||||
checksum: 10c0/9b1f65d5f08630ef444a89334370c394175b1543f157621b36a3bc5e5208946f3f0ab5d5e24c74e81f2ef54b55b742b4e5b439c561f62695ffb69a06b0bce8e1
|
||||
"idb-keyval@npm:^6.2.0":
|
||||
version: 6.2.1
|
||||
resolution: "idb-keyval@npm:6.2.1"
|
||||
checksum: 10c0/9f0c83703a365e00bd0b4ed6380ce509a06dedfc6ec39b2ba5740085069fd2f2ff5c14ba19356488e3612a2f9c49985971982d836460a982a5d0b4019eeba48a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -10238,7 +10217,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-electron@npm:^2.2.0":
|
||||
"is-electron@npm:^2.2.2":
|
||||
version: 2.2.2
|
||||
resolution: "is-electron@npm:2.2.2"
|
||||
checksum: 10c0/327bb373f7be01b16cdff3998b5ddaa87d28f576092affaa7fe0659571b3306fdd458afbf0683a66841e7999af13f46ad0e1b51647b469526cd05a4dd736438a
|
||||
|
|
@ -11218,28 +11197,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jpeg-autorotate@npm:^7.1.1":
|
||||
version: 7.1.1
|
||||
resolution: "jpeg-autorotate@npm:7.1.1"
|
||||
dependencies:
|
||||
colors: "npm:^1.4.0"
|
||||
glob: "npm:^7.1.6"
|
||||
jpeg-js: "npm:^0.4.2"
|
||||
piexifjs: "npm:^1.0.6"
|
||||
yargs-parser: "npm:^20.2.1"
|
||||
bin:
|
||||
jpeg-autorotate: src/cli.js
|
||||
checksum: 10c0/75328e15b7abcaf8b36c980495cb0b37ffabeb8921e8576312deac8139a9e8a66f85d9196f314120e4633c76623d1b595e65ca7a87b679511ffb804f880a1644
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jpeg-js@npm:^0.4.2":
|
||||
version: 0.4.4
|
||||
resolution: "jpeg-js@npm:0.4.4"
|
||||
checksum: 10c0/4d0d5097f8e55d8bbce6f1dc32ffaf3f43f321f6222e4e6490734fdc6d005322e3bd6fb992c2df7f5b587343b1441a1c333281dc3285bc9116e369fd2a2b43a7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "js-tokens@npm:4.0.0"
|
||||
|
|
@ -12464,9 +12421,9 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:^2.6.0":
|
||||
version: 2.6.11
|
||||
resolution: "node-fetch@npm:2.6.11"
|
||||
"node-fetch@npm:^2.6.9":
|
||||
version: 2.7.0
|
||||
resolution: "node-fetch@npm:2.7.0"
|
||||
dependencies:
|
||||
whatwg-url: "npm:^5.0.0"
|
||||
peerDependencies:
|
||||
|
|
@ -12474,7 +12431,7 @@ __metadata:
|
|||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
checksum: 10c0/3ec847ca43f678d07b80abfd85bdf06523c2554ee9a494c992c5fc61f5d9cde9f9f16aa33ff09a62f19eee9d54813b8850d7f054cdfee8b2daf789c57f8eeaea
|
||||
checksum: 10c0/b55786b6028208e6fbe594ccccc213cab67a72899c9234eb59dba51062a299ea853210fcf526998eaa2867b0963ad72338824450905679ff0fa304b8c5093ae8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -12843,7 +12800,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"opencollective-postinstall@npm:^2.0.2":
|
||||
"opencollective-postinstall@npm:^2.0.3":
|
||||
version: 2.0.3
|
||||
resolution: "opencollective-postinstall@npm:2.0.3"
|
||||
bin:
|
||||
|
|
@ -13326,13 +13283,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"piexifjs@npm:^1.0.6":
|
||||
version: 1.0.6
|
||||
resolution: "piexifjs@npm:1.0.6"
|
||||
checksum: 10c0/69a10fe09c08f1e67e653844ac79e720324a7fa34689b020359d60d98b3a601c070e1759df8f2d97d022298bd2f5b79eed4c92de86c5f215300c8a63adf947b1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pify@npm:^2.0.0":
|
||||
version: 2.3.0
|
||||
resolution: "pify@npm:2.3.0"
|
||||
|
|
@ -17159,31 +17109,28 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tesseract.js-core@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "tesseract.js-core@npm:2.2.0"
|
||||
checksum: 10c0/9ef569529f1ee96f8bf18388ef086d4940d3f02d28b4252df133a9bd36f6a9d140085e77a12ff1963cf9b4cd85bd1c644b61eca266ecfc51bb83adb30a1f11e3
|
||||
"tesseract.js-core@npm:^5.1.1":
|
||||
version: 5.1.1
|
||||
resolution: "tesseract.js-core@npm:5.1.1"
|
||||
checksum: 10c0/9324277aa1598e0707ddbc175d4312fb7b4569e3fc42adbb42a5a3a747b5711a83c810c597b375585ec3261df31270730a146ab61c63fd16472a21867472eb31
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tesseract.js@npm:^2.1.5":
|
||||
version: 2.1.5
|
||||
resolution: "tesseract.js@npm:2.1.5"
|
||||
"tesseract.js@npm:^5.1.1":
|
||||
version: 5.1.1
|
||||
resolution: "tesseract.js@npm:5.1.1"
|
||||
dependencies:
|
||||
blueimp-load-image: "npm:^3.0.0"
|
||||
bmp-js: "npm:^0.1.0"
|
||||
file-type: "npm:^12.4.1"
|
||||
idb-keyval: "npm:^3.2.0"
|
||||
is-electron: "npm:^2.2.0"
|
||||
idb-keyval: "npm:^6.2.0"
|
||||
is-electron: "npm:^2.2.2"
|
||||
is-url: "npm:^1.2.4"
|
||||
jpeg-autorotate: "npm:^7.1.1"
|
||||
node-fetch: "npm:^2.6.0"
|
||||
opencollective-postinstall: "npm:^2.0.2"
|
||||
node-fetch: "npm:^2.6.9"
|
||||
opencollective-postinstall: "npm:^2.0.3"
|
||||
regenerator-runtime: "npm:^0.13.3"
|
||||
resolve-url: "npm:^0.2.1"
|
||||
tesseract.js-core: "npm:^2.2.0"
|
||||
tesseract.js-core: "npm:^5.1.1"
|
||||
wasm-feature-detect: "npm:^1.2.11"
|
||||
zlibjs: "npm:^0.3.1"
|
||||
checksum: 10c0/b3aaee9189f3bc7f4217b83e110d0dd4d9afcafc3045b842f72b7ca9beb00bec732bc6b4b00eca14167c16b014c437fcf83dd272a640c9c8b5e1e9b55ea00ff5
|
||||
checksum: 10c0/46dd9c22c7c86d065c6fab07cdcc10e455c928e9d218e9e2a475eca4fc865bf60b052b3dc3503996fd195763184fb7deb2793083c84c869a6681e53d0329af36
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
@ -18022,6 +17969,13 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wasm-feature-detect@npm:^1.2.11":
|
||||
version: 1.8.0
|
||||
resolution: "wasm-feature-detect@npm:1.8.0"
|
||||
checksum: 10c0/2cb43e91bbf7aa7c121bc76b3133de3ab6dc4f482acc1d2dc46c528e8adb7a51c72df5c2aacf1d219f113c04efd1706f18274d5790542aa5dd49e0644e3ee665
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"watchpack-chokidar2@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "watchpack-chokidar2@npm:2.0.1"
|
||||
|
|
@ -18874,13 +18828,6 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs-parser@npm:^20.2.1":
|
||||
version: 20.2.9
|
||||
resolution: "yargs-parser@npm:20.2.9"
|
||||
checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs-parser@npm:^21.1.1":
|
||||
version: 21.1.1
|
||||
resolution: "yargs-parser@npm:21.1.1"
|
||||
|
|
|
|||
Loading…
Reference in New Issue