chore: seed migration objects batch 2

This commit is contained in:
dev
2026-10-18 13:59:32 +08:00
parent 3e00c3422d
commit 365e3284c2
47 changed files with 6331 additions and 3128 deletions
+62 -102
View File
@@ -1,15 +1,16 @@
// Single message, sent or received.
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Drafty } from 'tinode-sdk';
import Attachment from './attachment.jsx';
import LetterTile from './letter-tile.jsx';
import ReceivedMarker from './received-marker.jsx'
import UploadingImage from './uploading-image.jsx'
import { fullFormatter, quoteFormatter } from '../lib/formatters.js';
import { sanitizeImageUrl, sanitizeUrl } from '../lib/utils.js';
export default class ChatMessage extends React.Component {
class BaseChatMessage extends React.PureComponent {
constructor(props) {
super(props);
@@ -25,6 +26,17 @@ export default class ChatMessage extends React.Component {
this.handleFormButtonClick = this.handleFormButtonClick.bind(this);
this.handleContextClick = this.handleContextClick.bind(this);
this.handleCancelUpload = this.handleCancelUpload.bind(this);
this.handleQuoteClick = this.handleQuoteClick.bind(this);
this.formatterContext = {
getFormatter: (tp) => { return tp == 'QQ' ? quoteFormatter : null; },
formatMessage: props.intl.formatMessage.bind(props.intl),
viewportWidth: props.viewportWidth,
authorizeURL: props.tinode.authorizeURL.bind(props.tinode),
onImagePreview: this.handleImagePreview,
onFormButtonClick: this.handleFormButtonClick,
onQuoteClick: this.handleQuoteClick
};
}
handleImagePreview(e) {
@@ -59,9 +71,27 @@ export default class ChatMessage extends React.Component {
handleContextClick(e) {
e.preventDefault();
e.stopPropagation();
const menuItems = this.props.received == Tinode.MESSAGE_STATUS_FAILED ? ['menu_item_send_retry'] : [];
this.props.showContextMenu({ seq: this.props.seq, content: this.props.content,
y: e.pageY, x: e.pageX }, menuItems);
const menuItems = [];
if (this.props.received == Tinode.MESSAGE_STATUS_FAILED) {
menuItems.push('menu_item_send_retry');
}
if (this.props.userIsWriter &&
this.props.received > Tinode.MESSAGE_STATUS_FAILED &&
this.props.received < Tinode.MESSAGE_STATUS_DEL_RANGE) {
menuItems.push('menu_item_reply');
}
menuItems.push('menu_item_forward');
this.props.showContextMenu({
seq: this.props.seq,
content: this.props.content,
userFrom: this.props.userFrom,
userName: this.props.userName,
forwarded: this.props.forwarded,
y: e.pageY,
x: e.pageX,
pickReply: this.props.pickReply
}, menuItems);
}
handleProgress(ratio) {
@@ -72,18 +102,27 @@ export default class ChatMessage extends React.Component {
this.props.onCancelUpload(this.props.seq, this.props.uploader);
}
handleQuoteClick(e) {
e.preventDefault();
e.stopPropagation();
const replyToSeq = this.props.replyToSeq;
if (replyToSeq) {
this.props.onQuoteClick(replyToSeq);
}
}
render() {
const sideClass = this.props.deleted ? 'center' :
(this.props.sequence + ' ' + (this.props.response ? 'left' : 'right'));
const bubbleClass = (this.props.sequence == 'single' || this.props.sequence == 'last') ? 'bubble tip' : 'bubble';
const avatar = this.props.deleted ? null : (this.props.userAvatar || true);
const fullDisplay = (this.props.userFrom && this.props.response && !this.props.deleted &&
const fullDisplay = (this.props.isGroup && this.props.response && !this.props.deleted &&
(this.props.sequence == 'single' || this.props.sequence == 'last'));
let content = this.props.content;
const attachments = [];
if (this.props.mimeType == Drafty.getContentType() && Drafty.isValid(content)) {
Drafty.attachments(content, function(att, i) {
Drafty.attachments(content, (att, i) => {
if (att.mime == 'application/json') {
// Don't show json objects as attachments.
// They are not meant for users.
@@ -101,7 +140,8 @@ export default class ChatMessage extends React.Component {
onError={this.props.onError}
key={i} />);
}, this);
content = React.createElement(React.Fragment, null, Drafty.format(content, draftyFormatter, this));
const tree = Drafty.format(content, fullFormatter, this.formatterContext);
content = React.createElement(React.Fragment, null, tree);
} else if (this.props.deleted) {
// Message represents a range of deleted messages.
content = <><i className="material-icons gray">block</i> <i className="gray">
@@ -109,19 +149,18 @@ export default class ChatMessage extends React.Component {
defaultMessage="content deleted" description="Shown when messages are deleted" />
</i></>
} else if (typeof content != 'string') {
content = <>
<i className="material-icons gray">error_outline</i> <i className="gray">
<FormattedMessage id="invalid_content"
defaultMessage="invalid content" description="Shown when message is unreadable" /></i>
</>
content = <><i className="material-icons gray">warning_amber</i> <i className="gray">
<FormattedMessage id="invalid_content"
defaultMessage="invalid content" description="Shown when the message is unreadable" /></i></>
}
return (
<li className={sideClass}>
{this.props.userFrom && this.props.response ?
<li ref={this.props.innerRef} className={sideClass}>
{this.props.isGroup && this.props.response ?
<div className="avatar-box">
{fullDisplay ?
<LetterTile
tinode={this.props.tinode}
topic={this.props.userFrom}
title={this.props.userName}
avatar={avatar} /> :
@@ -152,10 +191,10 @@ export default class ChatMessage extends React.Component {
</div>
{fullDisplay ?
<div className="author">
<FormattedMessage id="user_not_found" defaultMessage="Not found"
description="In place of a user's full name when the user is not found.">{
(notFound) => {return this.props.userName || <i>{notFound}</i>}
}</FormattedMessage>
{this.props.userName ||
<i><FormattedMessage id="user_not_found" defaultMessage="Not found"
description="In place of a user's full name when the user is not found." /></i>
}
</div>
: null
}
@@ -165,86 +204,7 @@ export default class ChatMessage extends React.Component {
}
};
// Convert Drafty object to a tree of React elements.
import { BROKEN_IMAGE_SIZE, REM_SIZE } from '../config.js';
import { fitImageSize } from '../lib/blob-helpers.js';
const IntlChatMessage = injectIntl(BaseChatMessage);
const ChatMessage = React.forwardRef((props, ref) => <IntlChatMessage innerRef = {ref} {...props} />);
// Converts Drafty elements into React classes.
// 'this' is set by the caller.
function draftyFormatter(style, data, values, key) {
if (style == 'EX') {
// attachments are handled elsewhere.
return null;
}
let el = Drafty.tagName(style);
if (el) {
const attr = Drafty.attrValue(style, data) || {};
attr.key = key;
switch (style) {
case 'HL':
// Highlighted text. Assign class name.
attr.className = 'highlight';
break;
case 'IM':
// Additional processing for images
if (data) {
attr.className = 'inline-image';
const dim = fitImageSize(data.width, data.height,
Math.min(this.props.viewportWidth - REM_SIZE * 6.5, REM_SIZE * 34.5), REM_SIZE * 24, false) ||
{dstWidth: BROKEN_IMAGE_SIZE, dstHeight: BROKEN_IMAGE_SIZE};
attr.style = {
width: dim.dstWidth + 'px',
height: dim.dstHeight + 'px',
// Looks like a Chrome bug: broken image does not respect 'width' and 'height'.
minWidth: dim.dstWidth + 'px',
minHeight: dim.dstHeight + 'px'
};
if (!Drafty.isProcessing(data)) {
attr.src = this.props.tinode.authorizeURL(sanitizeImageUrl(attr.src));
attr.alt = data.name;
if (attr.src) {
attr.onClick = this.handleImagePreview;
attr.className += ' image-clickable';
attr.loading = 'lazy';
} else {
attr.src = 'img/broken_image.png';
}
} else {
// Use custom element instead of <img>.
el = UploadingImage;
}
}
break;
case 'BN':
// Button
attr.onClick = this.handleFormButtonClick;
let inner = React.Children.map(values, (child) => {
return typeof child == 'string' ? child : undefined;
});
if (!inner || inner.length == 0) {
inner = [attr.name]
}
// Get text which will be sent back when the button is clicked.
attr['data-title'] = inner.join('');
break;
case 'FM':
// Form
attr.className = 'bot-form';
break;
case 'RW':
// Form element formatting is dependent on element content.
break;
default:
if (el == '_UNKN') {
// Unknown element.
// TODO: make it prettier.
el = <><span className="material-icons">extension</span></>;
}
break;
}
return React.createElement(el, attr, values);
} else {
return values;
}
};
export default ChatMessage;