@selfcommunity/react-ui 0.7.0-alpha.317 → 0.7.0-alpha.319

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -64,6 +64,11 @@ export interface CommentObjectProps {
64
64
  * @default null
65
65
  */
66
66
  onDelete?: (comment: SCCommentType) => void;
67
+ /**
68
+ * Show all summary initially (otherwise it will be truncated)
69
+ * @default false
70
+ */
71
+ truncateContent?: boolean;
67
72
  /**
68
73
  * Props to spread to single comment object skeleton
69
74
  * @default {elevation: 0}
@@ -95,7 +95,7 @@ function CommentObject(inProps) {
95
95
  props: inProps,
96
96
  name: PREFIX
97
97
  });
98
- const { id = `comment_object_${props.commentObjectId ? props.commentObjectId : props.commentObject ? props.commentObject.id : ''}`, className, commentObjectId, commentObject, feedObjectId, feedObject, feedObjectType = types_1.SCContributionType.POST, commentReply, onOpenReply, onDelete, onVote, elevation = 0, CommentObjectSkeletonProps = { elevation, WidgetProps: { variant: 'outlined' } }, CommentObjectReplyProps = { elevation, WidgetProps: { variant: 'outlined' } }, linkableCommentDateTime = true, cacheStrategy = utils_1.CacheStrategies.NETWORK_ONLY } = props, rest = tslib_1.__rest(props, ["id", "className", "commentObjectId", "commentObject", "feedObjectId", "feedObject", "feedObjectType", "commentReply", "onOpenReply", "onDelete", "onVote", "elevation", "CommentObjectSkeletonProps", "CommentObjectReplyProps", "linkableCommentDateTime", "cacheStrategy"]);
98
+ const { id = `comment_object_${props.commentObjectId ? props.commentObjectId : props.commentObject ? props.commentObject.id : ''}`, className, commentObjectId, commentObject, feedObjectId, feedObject, feedObjectType = types_1.SCContributionType.POST, commentReply, onOpenReply, onDelete, onVote, elevation = 0, truncateContent = false, CommentObjectSkeletonProps = { elevation, WidgetProps: { variant: 'outlined' } }, CommentObjectReplyProps = { elevation, WidgetProps: { variant: 'outlined' } }, linkableCommentDateTime = true, cacheStrategy = utils_1.CacheStrategies.NETWORK_ONLY } = props, rest = tslib_1.__rest(props, ["id", "className", "commentObjectId", "commentObject", "feedObjectId", "feedObject", "feedObjectType", "commentReply", "onOpenReply", "onDelete", "onVote", "elevation", "truncateContent", "CommentObjectSkeletonProps", "CommentObjectReplyProps", "linkableCommentDateTime", "cacheStrategy"]);
99
99
  // CONTEXT
100
100
  const scContext = (0, react_core_1.useSCContext)();
101
101
  const scUserContext = (0, react_1.useContext)(react_core_1.SCUserContext);
@@ -329,7 +329,7 @@ function CommentObject(inProps) {
329
329
  // or the comment author is the logged user
330
330
  return null;
331
331
  }
332
- const commentHtml = comment.summary_html ? comment.summary_html : comment.html;
332
+ const commentHtml = comment.summary_html && truncateContent ? comment.summary_html : comment.html;
333
333
  const summaryHtmlTruncated = comment.summary_truncated ? comment.summary_truncated : false;
334
334
  const summaryHtml = (0, contribution_1.getContributionHtml)(commentHtml, scRoutingContext.url);
335
335
  return (react_1.default.createElement(react_1.default.Fragment, { key: comment.id },
@@ -342,7 +342,7 @@ function CommentObject(inProps) {
342
342
  react_1.default.createElement(react_core_1.Link, Object.assign({ className: classes.author }, (!comment.author.deleted && { to: scRoutingContext.url(react_core_1.SCRoutes.USER_PROFILE_ROUTE_NAME, comment.author) }), { onClick: comment.author.deleted ? () => setOpenAlert(true) : null }),
343
343
  react_1.default.createElement(material_1.Typography, { component: "span" }, comment.author.username)),
344
344
  react_1.default.createElement(material_1.Typography, { className: classes.textContent, variant: "body2", gutterBottom: true, dangerouslySetInnerHTML: { __html: summaryHtml } }),
345
- summaryHtmlTruncated && (react_1.default.createElement(react_core_1.Link, { to: scRoutingContext.url(react_core_1.SCRoutes.COMMENT_ROUTE_NAME, (0, contribution_1.getRouteData)(comment)), className: classes.showMoreContent },
345
+ summaryHtmlTruncated && truncateContent && (react_1.default.createElement(react_core_1.Link, { to: scRoutingContext.url(react_core_1.SCRoutes.COMMENT_ROUTE_NAME, (0, contribution_1.getRouteData)(comment)), className: classes.showMoreContent },
346
346
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.commentObject.showMore", defaultMessage: "ui.commentObject.showMore" })))),
347
347
  scUserContext.user && (react_1.default.createElement(material_1.Box, { className: classes.commentActionsMenu },
348
348
  react_1.default.createElement(ContributionActionsMenu_1.default, { commentObject: comment, onRestoreContribution: handleRestore, onHideContribution: handleHide, onDeleteContribution: handleDelete, onEditContribution: handleEdit })))),
@@ -8,10 +8,11 @@ const LexicalComposerContext_1 = require("@lexical/react/LexicalComposerContext"
8
8
  const useLexicalNodeSelection_1 = require("@lexical/react/useLexicalNodeSelection");
9
9
  const utils_1 = require("@lexical/utils");
10
10
  /**
11
- * Limit the width of an image
11
+ * Limit the width of an image (min/max)
12
12
  * Used to compute the padding-bottom of the div that wrap the img
13
13
  */
14
- const IMAGE_WIDTH_THRESHOLD = 500;
14
+ const IMAGE_MAX_WIDTH_THRESHOLD = 500;
15
+ const IMAGE_MIN_WIDTH_THRESHOLD = 375;
15
16
  /**
16
17
  * Calc aspect-ratio of image
17
18
  * @param width
@@ -35,13 +36,9 @@ function useSuspenseImage(src) {
35
36
  }
36
37
  function LazyImage({ altText, className, imageRef, src, width, height }) {
37
38
  useSuspenseImage(src);
38
- const aspectRatio = getAspectRatio(IMAGE_WIDTH_THRESHOLD, height);
39
+ const aspectRatio = getAspectRatio(width >= IMAGE_MAX_WIDTH_THRESHOLD ? width : IMAGE_MAX_WIDTH_THRESHOLD, height);
39
40
  return (react_1.default.createElement("div", { draggable: false, className: className, style: { position: 'relative', paddingBottom: `${100 / aspectRatio}%` } },
40
- react_1.default.createElement("img", { src: src, alt: altText, ref: imageRef, style: {
41
- position: 'absolute',
42
- height: `100%`,
43
- width: `100%`
44
- } })));
41
+ react_1.default.createElement("img", { src: src, alt: altText, ref: imageRef, style: Object.assign({ position: 'absolute' }, (width < IMAGE_MIN_WIDTH_THRESHOLD ? { paddingRight: IMAGE_MIN_WIDTH_THRESHOLD - width } : {})) })));
45
42
  }
46
43
  function ImageComponent({ src, altText, nodeKey, width, height }) {
47
44
  const imageRef = (0, react_1.useRef)(null);
@@ -32,11 +32,13 @@ function Image({ editor, className = '' }) {
32
32
  return file.type.startsWith('image/');
33
33
  };
34
34
  const handleUploadSuccess = (media) => {
35
- const image = media.image_thumbnail ? media.image_thumbnail : {
36
- url: media.image,
37
- width: media.image_width,
38
- height: media.image_height
39
- };
35
+ const image = media.image_thumbnail
36
+ ? media.image_thumbnail
37
+ : {
38
+ url: media.image,
39
+ width: media.image_width,
40
+ height: media.image_height
41
+ };
40
42
  const data = {
41
43
  altText: media.title,
42
44
  src: image.url,
@@ -42,7 +42,7 @@ function Activities(inProps) {
42
42
  feedObject,
43
43
  feedObjectType,
44
44
  cacheStrategy,
45
- pageSize: 3,
45
+ pageSize: 2,
46
46
  orderBy: selectedActivities === feedObject_1.SCFeedObjectActivitiesType.CONNECTIONS_COMMENTS
47
47
  ? comments_1.SCCommentsOrderBy.CONNECTION_DESC
48
48
  : selectedActivities === feedObject_1.SCFeedObjectActivitiesType.FIRST_COMMENTS
@@ -50,7 +50,7 @@ function Activities(inProps) {
50
50
  : comments_1.SCCommentsOrderBy.ADDED_AT_DESC
51
51
  });
52
52
  const objId = commentsObject.feedObject ? commentsObject.feedObject.id : null;
53
- const skeletonsCount = Math.min(3, commentsObject.feedObject ? commentsObject.feedObject.comment_count : 3);
53
+ const skeletonsCount = Math.min(3, commentsObject.feedObject ? commentsObject.feedObject.comment_count : 2);
54
54
  const existFeedObjectActivities = feedObjectActivities && feedObjectActivities.length > 0;
55
55
  /**
56
56
  * Sync activities type if prop change
@@ -80,11 +80,17 @@ function Activities(inProps) {
80
80
  function renderRelevantActivities() {
81
81
  return react_1.default.createElement(RelevantActivities_1.default, { activities: feedObjectActivities });
82
82
  }
83
+ /**
84
+ * Load comments
85
+ */
86
+ function handleNext() {
87
+ commentsObject.getNextPage();
88
+ }
83
89
  /**
84
90
  * Render comments of feedObject
85
91
  */
86
92
  function renderComments() {
87
- return (react_1.default.createElement(react_1.default.Fragment, null, (commentsObject.feedObject.comment_count > 0 || comments.length > 0) && (react_1.default.createElement(CommentsObject_1.default, Object.assign({ feedObject: commentsObject.feedObject, comments: commentsObject.comments, startComments: comments, next: commentsObject.next, isLoadingNext: commentsObject.isLoadingNext, handleNext: commentsObject.getNextPage, totalLoadedComments: commentsObject.comments.length + comments.length, totalComments: commentsObject.feedObject.comment_count, hideAdvertising: true, cacheStrategy: cacheStrategy }, CommentsObjectProps, { CommentsObjectSkeletonProps: { count: skeletonsCount }, CommentComponentProps: Object.assign(Object.assign({}, CommentComponentProps), { cacheStrategy }) })))));
93
+ return (react_1.default.createElement(react_1.default.Fragment, null, (commentsObject.feedObject.comment_count > 0 || comments.length > 0) && (react_1.default.createElement(CommentsObject_1.default, Object.assign({ feedObject: commentsObject.feedObject, comments: commentsObject.comments, startComments: comments, next: commentsObject.next, isLoadingNext: commentsObject.isLoadingNext, handleNext: handleNext, totalLoadedComments: commentsObject.comments.length + comments.length, totalComments: commentsObject.feedObject.comment_count, hideAdvertising: true }, CommentsObjectProps, { cacheStrategy: cacheStrategy, CommentsObjectSkeletonProps: { count: skeletonsCount }, CommentComponentProps: Object.assign(Object.assign(Object.assign({}, CommentComponentProps), { cacheStrategy }), (CommentsObjectProps.CommentComponentProps ? CommentsObjectProps.CommentComponentProps : {})) })))));
88
94
  }
89
95
  /**
90
96
  * Renders root object
@@ -59,6 +59,11 @@ export interface FeedObjectProps extends CardProps, VirtualScrollerItemProps {
59
59
  * @default false
60
60
  */
61
61
  hideFollowAction?: boolean;
62
+ /**
63
+ * Show all summary initially (otherwise it will be truncated)
64
+ * @default false
65
+ */
66
+ summaryExpanded?: boolean;
62
67
  /**
63
68
  * Show activities as default
64
69
  * @default false
@@ -133,7 +133,7 @@ function FeedObject(inProps) {
133
133
  props: inProps,
134
134
  name: PREFIX
135
135
  });
136
- const { id = `feed_object_${props.feedObjectId ? props.feedObjectId : props.feedObject ? props.feedObject.id : ''}`, className = null, feedObjectId = null, feedObject = null, feedObjectType = types_1.SCContributionType.DISCUSSION, feedObjectActivities = null, cacheStrategy = utils_1.CacheStrategies.CACHE_FIRST, markRead = false, template = feedObject_1.SCFeedObjectTemplateType.PREVIEW, hideFollowAction = false, activitiesExpanded = true, activitiesExpandedType, hideParticipantsPreview = false, pollVisible = false, FollowButtonProps = {}, FeedObjectSkeletonProps = { elevation: 0 }, ActionsProps = {}, CommentObjectReplyComponent = CommentObjectReply_1.default, CommentObjectReplyComponentProps = { WidgetProps: { variant: 'outlined' } }, CommentComponentProps = { variant: 'outlined' }, CommentObjectSkeletonProps = { elevation: 0, WidgetProps: { variant: 'outlined' } }, ContributionActionsMenuProps = {}, MediasPreviewProps = {}, ActivitiesProps = { cacheStrategy }, PollObjectProps = { elevation: 0 }, ContributorsFeedObjectProps = {}, onReply, onHeightChange, onStateChange } = props, rest = tslib_1.__rest(props, ["id", "className", "feedObjectId", "feedObject", "feedObjectType", "feedObjectActivities", "cacheStrategy", "markRead", "template", "hideFollowAction", "activitiesExpanded", "activitiesExpandedType", "hideParticipantsPreview", "pollVisible", "FollowButtonProps", "FeedObjectSkeletonProps", "ActionsProps", "CommentObjectReplyComponent", "CommentObjectReplyComponentProps", "CommentComponentProps", "CommentObjectSkeletonProps", "ContributionActionsMenuProps", "MediasPreviewProps", "ActivitiesProps", "PollObjectProps", "ContributorsFeedObjectProps", "onReply", "onHeightChange", "onStateChange"]);
136
+ const { id = `feed_object_${props.feedObjectId ? props.feedObjectId : props.feedObject ? props.feedObject.id : ''}`, className = null, feedObjectId = null, feedObject = null, feedObjectType = types_1.SCContributionType.DISCUSSION, feedObjectActivities = null, cacheStrategy = utils_1.CacheStrategies.CACHE_FIRST, markRead = false, template = feedObject_1.SCFeedObjectTemplateType.PREVIEW, hideFollowAction = false, summaryExpanded = false, activitiesExpanded = true, activitiesExpandedType, hideParticipantsPreview = false, pollVisible = false, FollowButtonProps = {}, FeedObjectSkeletonProps = { elevation: 0 }, ActionsProps = {}, CommentObjectReplyComponent = CommentObjectReply_1.default, CommentObjectReplyComponentProps = { WidgetProps: { variant: 'outlined' } }, CommentComponentProps = { variant: 'outlined' }, CommentObjectSkeletonProps = { elevation: 0, WidgetProps: { variant: 'outlined' } }, ContributionActionsMenuProps = {}, MediasPreviewProps = {}, ActivitiesProps = { cacheStrategy }, PollObjectProps = { elevation: 0 }, ContributorsFeedObjectProps = {}, onReply, onHeightChange, onStateChange } = props, rest = tslib_1.__rest(props, ["id", "className", "feedObjectId", "feedObject", "feedObjectType", "feedObjectActivities", "cacheStrategy", "markRead", "template", "hideFollowAction", "summaryExpanded", "activitiesExpanded", "activitiesExpandedType", "hideParticipantsPreview", "pollVisible", "FollowButtonProps", "FeedObjectSkeletonProps", "ActionsProps", "CommentObjectReplyComponent", "CommentObjectReplyComponentProps", "CommentComponentProps", "CommentObjectSkeletonProps", "ContributionActionsMenuProps", "MediasPreviewProps", "ActivitiesProps", "PollObjectProps", "ContributorsFeedObjectProps", "onReply", "onHeightChange", "onStateChange"]);
137
137
  // CONTEXT
138
138
  const scContext = (0, react_core_1.useSCContext)();
139
139
  const scRoutingContext = (0, react_core_1.useSCRouting)();
@@ -155,7 +155,7 @@ function FeedObject(inProps) {
155
155
  const [comments, setComments] = (0, react_1.useState)([]);
156
156
  const [isReplying, setIsReplying] = (0, react_1.useState)(false);
157
157
  const [selectedActivities, setSelectedActivities] = (0, react_1.useState)(getInitialSelectedActivitiesType());
158
- const [expanded, setExpanded] = (0, react_1.useState)(false);
158
+ const [expanded, setExpanded] = (0, react_1.useState)(summaryExpanded);
159
159
  // INTL
160
160
  const intl = (0, react_intl_1.useIntl)();
161
161
  /**
@@ -207,6 +207,13 @@ function FeedObject(inProps) {
207
207
  const handleTogglePollVisibility = (0, react_1.useCallback)((visible) => {
208
208
  notifyFeedChanges({ pollVisible: visible });
209
209
  }, [pollVisible, notifyFeedChanges]);
210
+ /**
211
+ * Handle toggle summary
212
+ */
213
+ const handleToggleSummary = (0, react_1.useCallback)(() => {
214
+ setExpanded(!expanded);
215
+ notifyFeedChanges({ summaryExpanded: !expanded });
216
+ }, [expanded, notifyFeedChanges]);
210
217
  /**
211
218
  * Render header action
212
219
  * if author = authenticated user -> render edit action
@@ -384,7 +391,7 @@ function FeedObject(inProps) {
384
391
  react_1.default.createElement(material_1.Typography, { component: "div", className: classes.text, variant: "body2", gutterBottom: true, dangerouslySetInnerHTML: {
385
392
  __html: summaryHtml
386
393
  } })),
387
- !expanded && summaryHtmlTruncated && (react_1.default.createElement(material_1.Button, { size: "small", variant: "text", color: "inherit", onClick: () => setExpanded(!expanded) },
394
+ !expanded && summaryHtmlTruncated && (react_1.default.createElement(material_1.Button, { size: "small", variant: "text", color: "inherit", onClick: handleToggleSummary },
388
395
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.feedObject.content.showMore", defaultMessage: "ui.feedObject.content.showMore" })))));
389
396
  }
390
397
  else if (template === feedObject_1.SCFeedObjectTemplateType.DETAIL) {
@@ -397,7 +404,7 @@ function FeedObject(inProps) {
397
404
  react_1.default.createElement(material_1.Typography, { component: "span", dangerouslySetInnerHTML: {
398
405
  __html: summaryHtml
399
406
  } }),
400
- !expanded && summaryHtmlTruncated && (react_1.default.createElement(material_1.Button, { size: "small", variant: "text", color: "inherit", onClick: () => setExpanded(!expanded) },
407
+ !expanded && summaryHtmlTruncated && (react_1.default.createElement(material_1.Button, { size: "small", variant: "text", color: "inherit", onClick: handleToggleSummary },
401
408
  react_1.default.createElement(react_intl_1.FormattedMessage, { id: "ui.feedObject.content.showMore", defaultMessage: "ui.feedObject.content.showMore" })))));
402
409
  }
403
410
  }, [obj, template, expanded]);
@@ -451,7 +458,7 @@ function FeedObject(inProps) {
451
458
  template === feedObject_1.SCFeedObjectTemplateType.PREVIEW && (obj.comment_count > 0 || (feedObjectActivities && feedObjectActivities.length > 0)) && (react_1.default.createElement(material_1.Collapse, { in: expandedActivities, timeout: "auto", classes: { root: classes.activitiesSection } },
452
459
  react_1.default.createElement(CardContent_1.default, { className: classes.activitiesContent },
453
460
  react_1.default.createElement(Activities_1.default, Object.assign({ feedObject: obj, key: selectedActivities, feedObjectActivities: feedObjectActivities, activitiesType: selectedActivities, onSetSelectedActivities: handleSelectedActivities, comments: comments, CommentsObjectProps: {
454
- CommentComponentProps: Object.assign({ onDelete: handleDeleteComment }, CommentComponentProps),
461
+ CommentComponentProps: Object.assign({ onDelete: handleDeleteComment, truncateContent: true }, CommentComponentProps),
455
462
  CommentObjectSkeletonProps: CommentObjectSkeletonProps
456
463
  }, cacheStrategy: cacheStrategy }, ActivitiesProps))))),
457
464
  composerOpen && (react_1.default.createElement(Composer_1.default, { open: composerOpen, feedObject: obj, onClose: handleToggleEdit, onSuccess: handleEditSuccess, maxWidth: "sm", fullWidth: true, scroll: "body" })))) : (react_1.default.createElement(Skeleton_1.default, Object.assign({ template: template }, FeedObjectSkeletonProps)))));
@@ -99,9 +99,13 @@ function PrivateMessageEditor(inProps) {
99
99
  setMessage(event.target.value);
100
100
  };
101
101
  const handleEmojiClick = (emojiData, event) => {
102
- const cursor = ref.current.selectionStart;
103
- const text = message.slice(0, cursor) + emojiData.emoji;
104
- setMessage(text);
102
+ const cursorPosition = ref.current.selectionEnd;
103
+ const start = ref.current.value.substring(0, ref.current.selectionStart);
104
+ const end = ref.current.value.substring(ref.current.selectionStart);
105
+ setMessage(start + emojiData.emoji + end);
106
+ setTimeout(() => {
107
+ ref.current.selectionStart = ref.current.selectionEnd = cursorPosition + emojiData.emoji.length;
108
+ }, 50);
105
109
  };
106
110
  // EFFECTS
107
111
  (0, react_1.useEffect)(() => {
@@ -129,8 +133,7 @@ function PrivateMessageEditor(inProps) {
129
133
  return (react_1.default.createElement(react_1.default.Fragment, null, openMediaSection ? (react_1.default.createElement(MessageMediaUploader_1.default, { className: classes.uploadMediaSection, open: openMediaSection, onClose: handleMediaSectionClose, forwardMessageFile: handleMessageFiles, isUploading: setUploading, action: react_1.default.createElement(material_1.IconButton, { disabled: (!message && !messageFiles.length) || uploading, onClick: handleMessageSend },
130
134
  react_1.default.createElement(Icon_1.default, null, "send")) })) : (react_1.default.createElement(react_1.default.Fragment, null,
131
135
  openEmojiSection && react_1.default.createElement(EmojiPicker_1.default, { className: classes.emojiSection, onEmojiClick: handleEmojiClick, width: "100%", searchDisabled: true }),
132
- react_1.default.createElement(material_1.TextField, { size: "small", disabled: Boolean(messageFiles.length) || openMediaSection, ref: ref, className: classes.messageInput, multiline: true, placeholder: `${intl.formatMessage(messages.placeholder)}`, value: message, onChange: handleMessageInput, maxRows: 2, onSelect: () => setOpenEmojiSection(false), InputProps: {
133
- disableUnderline: true,
136
+ react_1.default.createElement(material_1.TextField, { size: "small", inputRef: ref, disabled: Boolean(messageFiles.length) || openMediaSection, className: classes.messageInput, multiline: true, placeholder: `${intl.formatMessage(messages.placeholder)}`, value: message, onChange: handleMessageInput, maxRows: 2, onSelect: () => setOpenEmojiSection(false), InputProps: {
134
137
  startAdornment: (react_1.default.createElement(react_1.default.Fragment, null,
135
138
  react_1.default.createElement(material_1.IconButton, { disabled: openMediaSection, onClick: () => setOpenEmojiSection(!openEmojiSection) },
136
139
  react_1.default.createElement(Icon_1.default, null, "sentiment_satisfied_alt")),
@@ -64,6 +64,11 @@ export interface CommentObjectProps {
64
64
  * @default null
65
65
  */
66
66
  onDelete?: (comment: SCCommentType) => void;
67
+ /**
68
+ * Show all summary initially (otherwise it will be truncated)
69
+ * @default false
70
+ */
71
+ truncateContent?: boolean;
67
72
  /**
68
73
  * Props to spread to single comment object skeleton
69
74
  * @default {elevation: 0}
@@ -93,7 +93,7 @@ export default function CommentObject(inProps) {
93
93
  props: inProps,
94
94
  name: PREFIX
95
95
  });
96
- const { id = `comment_object_${props.commentObjectId ? props.commentObjectId : props.commentObject ? props.commentObject.id : ''}`, className, commentObjectId, commentObject, feedObjectId, feedObject, feedObjectType = SCContributionType.POST, commentReply, onOpenReply, onDelete, onVote, elevation = 0, CommentObjectSkeletonProps = { elevation, WidgetProps: { variant: 'outlined' } }, CommentObjectReplyProps = { elevation, WidgetProps: { variant: 'outlined' } }, linkableCommentDateTime = true, cacheStrategy = CacheStrategies.NETWORK_ONLY } = props, rest = __rest(props, ["id", "className", "commentObjectId", "commentObject", "feedObjectId", "feedObject", "feedObjectType", "commentReply", "onOpenReply", "onDelete", "onVote", "elevation", "CommentObjectSkeletonProps", "CommentObjectReplyProps", "linkableCommentDateTime", "cacheStrategy"]);
96
+ const { id = `comment_object_${props.commentObjectId ? props.commentObjectId : props.commentObject ? props.commentObject.id : ''}`, className, commentObjectId, commentObject, feedObjectId, feedObject, feedObjectType = SCContributionType.POST, commentReply, onOpenReply, onDelete, onVote, elevation = 0, truncateContent = false, CommentObjectSkeletonProps = { elevation, WidgetProps: { variant: 'outlined' } }, CommentObjectReplyProps = { elevation, WidgetProps: { variant: 'outlined' } }, linkableCommentDateTime = true, cacheStrategy = CacheStrategies.NETWORK_ONLY } = props, rest = __rest(props, ["id", "className", "commentObjectId", "commentObject", "feedObjectId", "feedObject", "feedObjectType", "commentReply", "onOpenReply", "onDelete", "onVote", "elevation", "truncateContent", "CommentObjectSkeletonProps", "CommentObjectReplyProps", "linkableCommentDateTime", "cacheStrategy"]);
97
97
  // CONTEXT
98
98
  const scContext = useSCContext();
99
99
  const scUserContext = useContext(SCUserContext);
@@ -327,7 +327,7 @@ export default function CommentObject(inProps) {
327
327
  // or the comment author is the logged user
328
328
  return null;
329
329
  }
330
- const commentHtml = comment.summary_html ? comment.summary_html : comment.html;
330
+ const commentHtml = comment.summary_html && truncateContent ? comment.summary_html : comment.html;
331
331
  const summaryHtmlTruncated = comment.summary_truncated ? comment.summary_truncated : false;
332
332
  const summaryHtml = getContributionHtml(commentHtml, scRoutingContext.url);
333
333
  return (React.createElement(React.Fragment, { key: comment.id },
@@ -340,7 +340,7 @@ export default function CommentObject(inProps) {
340
340
  React.createElement(Link, Object.assign({ className: classes.author }, (!comment.author.deleted && { to: scRoutingContext.url(SCRoutes.USER_PROFILE_ROUTE_NAME, comment.author) }), { onClick: comment.author.deleted ? () => setOpenAlert(true) : null }),
341
341
  React.createElement(Typography, { component: "span" }, comment.author.username)),
342
342
  React.createElement(Typography, { className: classes.textContent, variant: "body2", gutterBottom: true, dangerouslySetInnerHTML: { __html: summaryHtml } }),
343
- summaryHtmlTruncated && (React.createElement(Link, { to: scRoutingContext.url(SCRoutes.COMMENT_ROUTE_NAME, getRouteData(comment)), className: classes.showMoreContent },
343
+ summaryHtmlTruncated && truncateContent && (React.createElement(Link, { to: scRoutingContext.url(SCRoutes.COMMENT_ROUTE_NAME, getRouteData(comment)), className: classes.showMoreContent },
344
344
  React.createElement(FormattedMessage, { id: "ui.commentObject.showMore", defaultMessage: "ui.commentObject.showMore" })))),
345
345
  scUserContext.user && (React.createElement(Box, { className: classes.commentActionsMenu },
346
346
  React.createElement(ContributionActionsMenu, { commentObject: comment, onRestoreContribution: handleRestore, onHideContribution: handleHide, onDeleteContribution: handleDelete, onEditContribution: handleEdit })))),
@@ -4,10 +4,11 @@ import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext
4
4
  import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
5
5
  import { mergeRegister } from '@lexical/utils';
6
6
  /**
7
- * Limit the width of an image
7
+ * Limit the width of an image (min/max)
8
8
  * Used to compute the padding-bottom of the div that wrap the img
9
9
  */
10
- const IMAGE_WIDTH_THRESHOLD = 500;
10
+ const IMAGE_MAX_WIDTH_THRESHOLD = 500;
11
+ const IMAGE_MIN_WIDTH_THRESHOLD = 375;
11
12
  /**
12
13
  * Calc aspect-ratio of image
13
14
  * @param width
@@ -31,13 +32,9 @@ function useSuspenseImage(src) {
31
32
  }
32
33
  function LazyImage({ altText, className, imageRef, src, width, height }) {
33
34
  useSuspenseImage(src);
34
- const aspectRatio = getAspectRatio(IMAGE_WIDTH_THRESHOLD, height);
35
+ const aspectRatio = getAspectRatio(width >= IMAGE_MAX_WIDTH_THRESHOLD ? width : IMAGE_MAX_WIDTH_THRESHOLD, height);
35
36
  return (React.createElement("div", { draggable: false, className: className, style: { position: 'relative', paddingBottom: `${100 / aspectRatio}%` } },
36
- React.createElement("img", { src: src, alt: altText, ref: imageRef, style: {
37
- position: 'absolute',
38
- height: `100%`,
39
- width: `100%`
40
- } })));
37
+ React.createElement("img", { src: src, alt: altText, ref: imageRef, style: Object.assign({ position: 'absolute' }, (width < IMAGE_MIN_WIDTH_THRESHOLD ? { paddingRight: IMAGE_MIN_WIDTH_THRESHOLD - width } : {})) })));
41
38
  }
42
39
  function ImageComponent({ src, altText, nodeKey, width, height }) {
43
40
  const imageRef = useRef(null);
@@ -29,11 +29,13 @@ function Image({ editor, className = '' }) {
29
29
  return file.type.startsWith('image/');
30
30
  };
31
31
  const handleUploadSuccess = (media) => {
32
- const image = media.image_thumbnail ? media.image_thumbnail : {
33
- url: media.image,
34
- width: media.image_width,
35
- height: media.image_height
36
- };
32
+ const image = media.image_thumbnail
33
+ ? media.image_thumbnail
34
+ : {
35
+ url: media.image,
36
+ width: media.image_width,
37
+ height: media.image_height
38
+ };
37
39
  const data = {
38
40
  altText: media.title,
39
41
  src: image.url,
@@ -40,7 +40,7 @@ export default function Activities(inProps) {
40
40
  feedObject,
41
41
  feedObjectType,
42
42
  cacheStrategy,
43
- pageSize: 3,
43
+ pageSize: 2,
44
44
  orderBy: selectedActivities === SCFeedObjectActivitiesType.CONNECTIONS_COMMENTS
45
45
  ? SCCommentsOrderBy.CONNECTION_DESC
46
46
  : selectedActivities === SCFeedObjectActivitiesType.FIRST_COMMENTS
@@ -48,7 +48,7 @@ export default function Activities(inProps) {
48
48
  : SCCommentsOrderBy.ADDED_AT_DESC
49
49
  });
50
50
  const objId = commentsObject.feedObject ? commentsObject.feedObject.id : null;
51
- const skeletonsCount = Math.min(3, commentsObject.feedObject ? commentsObject.feedObject.comment_count : 3);
51
+ const skeletonsCount = Math.min(3, commentsObject.feedObject ? commentsObject.feedObject.comment_count : 2);
52
52
  const existFeedObjectActivities = feedObjectActivities && feedObjectActivities.length > 0;
53
53
  /**
54
54
  * Sync activities type if prop change
@@ -78,11 +78,17 @@ export default function Activities(inProps) {
78
78
  function renderRelevantActivities() {
79
79
  return React.createElement(RelevantActivities, { activities: feedObjectActivities });
80
80
  }
81
+ /**
82
+ * Load comments
83
+ */
84
+ function handleNext() {
85
+ commentsObject.getNextPage();
86
+ }
81
87
  /**
82
88
  * Render comments of feedObject
83
89
  */
84
90
  function renderComments() {
85
- return (React.createElement(React.Fragment, null, (commentsObject.feedObject.comment_count > 0 || comments.length > 0) && (React.createElement(CommentsObject, Object.assign({ feedObject: commentsObject.feedObject, comments: commentsObject.comments, startComments: comments, next: commentsObject.next, isLoadingNext: commentsObject.isLoadingNext, handleNext: commentsObject.getNextPage, totalLoadedComments: commentsObject.comments.length + comments.length, totalComments: commentsObject.feedObject.comment_count, hideAdvertising: true, cacheStrategy: cacheStrategy }, CommentsObjectProps, { CommentsObjectSkeletonProps: { count: skeletonsCount }, CommentComponentProps: Object.assign(Object.assign({}, CommentComponentProps), { cacheStrategy }) })))));
91
+ return (React.createElement(React.Fragment, null, (commentsObject.feedObject.comment_count > 0 || comments.length > 0) && (React.createElement(CommentsObject, Object.assign({ feedObject: commentsObject.feedObject, comments: commentsObject.comments, startComments: comments, next: commentsObject.next, isLoadingNext: commentsObject.isLoadingNext, handleNext: handleNext, totalLoadedComments: commentsObject.comments.length + comments.length, totalComments: commentsObject.feedObject.comment_count, hideAdvertising: true }, CommentsObjectProps, { cacheStrategy: cacheStrategy, CommentsObjectSkeletonProps: { count: skeletonsCount }, CommentComponentProps: Object.assign(Object.assign(Object.assign({}, CommentComponentProps), { cacheStrategy }), (CommentsObjectProps.CommentComponentProps ? CommentsObjectProps.CommentComponentProps : {})) })))));
86
92
  }
87
93
  /**
88
94
  * Renders root object
@@ -59,6 +59,11 @@ export interface FeedObjectProps extends CardProps, VirtualScrollerItemProps {
59
59
  * @default false
60
60
  */
61
61
  hideFollowAction?: boolean;
62
+ /**
63
+ * Show all summary initially (otherwise it will be truncated)
64
+ * @default false
65
+ */
66
+ summaryExpanded?: boolean;
62
67
  /**
63
68
  * Show activities as default
64
69
  * @default false
@@ -131,7 +131,7 @@ export default function FeedObject(inProps) {
131
131
  props: inProps,
132
132
  name: PREFIX
133
133
  });
134
- const { id = `feed_object_${props.feedObjectId ? props.feedObjectId : props.feedObject ? props.feedObject.id : ''}`, className = null, feedObjectId = null, feedObject = null, feedObjectType = SCContributionType.DISCUSSION, feedObjectActivities = null, cacheStrategy = CacheStrategies.CACHE_FIRST, markRead = false, template = SCFeedObjectTemplateType.PREVIEW, hideFollowAction = false, activitiesExpanded = true, activitiesExpandedType, hideParticipantsPreview = false, pollVisible = false, FollowButtonProps = {}, FeedObjectSkeletonProps = { elevation: 0 }, ActionsProps = {}, CommentObjectReplyComponent = CommentObjectReply, CommentObjectReplyComponentProps = { WidgetProps: { variant: 'outlined' } }, CommentComponentProps = { variant: 'outlined' }, CommentObjectSkeletonProps = { elevation: 0, WidgetProps: { variant: 'outlined' } }, ContributionActionsMenuProps = {}, MediasPreviewProps = {}, ActivitiesProps = { cacheStrategy }, PollObjectProps = { elevation: 0 }, ContributorsFeedObjectProps = {}, onReply, onHeightChange, onStateChange } = props, rest = __rest(props, ["id", "className", "feedObjectId", "feedObject", "feedObjectType", "feedObjectActivities", "cacheStrategy", "markRead", "template", "hideFollowAction", "activitiesExpanded", "activitiesExpandedType", "hideParticipantsPreview", "pollVisible", "FollowButtonProps", "FeedObjectSkeletonProps", "ActionsProps", "CommentObjectReplyComponent", "CommentObjectReplyComponentProps", "CommentComponentProps", "CommentObjectSkeletonProps", "ContributionActionsMenuProps", "MediasPreviewProps", "ActivitiesProps", "PollObjectProps", "ContributorsFeedObjectProps", "onReply", "onHeightChange", "onStateChange"]);
134
+ const { id = `feed_object_${props.feedObjectId ? props.feedObjectId : props.feedObject ? props.feedObject.id : ''}`, className = null, feedObjectId = null, feedObject = null, feedObjectType = SCContributionType.DISCUSSION, feedObjectActivities = null, cacheStrategy = CacheStrategies.CACHE_FIRST, markRead = false, template = SCFeedObjectTemplateType.PREVIEW, hideFollowAction = false, summaryExpanded = false, activitiesExpanded = true, activitiesExpandedType, hideParticipantsPreview = false, pollVisible = false, FollowButtonProps = {}, FeedObjectSkeletonProps = { elevation: 0 }, ActionsProps = {}, CommentObjectReplyComponent = CommentObjectReply, CommentObjectReplyComponentProps = { WidgetProps: { variant: 'outlined' } }, CommentComponentProps = { variant: 'outlined' }, CommentObjectSkeletonProps = { elevation: 0, WidgetProps: { variant: 'outlined' } }, ContributionActionsMenuProps = {}, MediasPreviewProps = {}, ActivitiesProps = { cacheStrategy }, PollObjectProps = { elevation: 0 }, ContributorsFeedObjectProps = {}, onReply, onHeightChange, onStateChange } = props, rest = __rest(props, ["id", "className", "feedObjectId", "feedObject", "feedObjectType", "feedObjectActivities", "cacheStrategy", "markRead", "template", "hideFollowAction", "summaryExpanded", "activitiesExpanded", "activitiesExpandedType", "hideParticipantsPreview", "pollVisible", "FollowButtonProps", "FeedObjectSkeletonProps", "ActionsProps", "CommentObjectReplyComponent", "CommentObjectReplyComponentProps", "CommentComponentProps", "CommentObjectSkeletonProps", "ContributionActionsMenuProps", "MediasPreviewProps", "ActivitiesProps", "PollObjectProps", "ContributorsFeedObjectProps", "onReply", "onHeightChange", "onStateChange"]);
135
135
  // CONTEXT
136
136
  const scContext = useSCContext();
137
137
  const scRoutingContext = useSCRouting();
@@ -153,7 +153,7 @@ export default function FeedObject(inProps) {
153
153
  const [comments, setComments] = useState([]);
154
154
  const [isReplying, setIsReplying] = useState(false);
155
155
  const [selectedActivities, setSelectedActivities] = useState(getInitialSelectedActivitiesType());
156
- const [expanded, setExpanded] = useState(false);
156
+ const [expanded, setExpanded] = useState(summaryExpanded);
157
157
  // INTL
158
158
  const intl = useIntl();
159
159
  /**
@@ -205,6 +205,13 @@ export default function FeedObject(inProps) {
205
205
  const handleTogglePollVisibility = useCallback((visible) => {
206
206
  notifyFeedChanges({ pollVisible: visible });
207
207
  }, [pollVisible, notifyFeedChanges]);
208
+ /**
209
+ * Handle toggle summary
210
+ */
211
+ const handleToggleSummary = useCallback(() => {
212
+ setExpanded(!expanded);
213
+ notifyFeedChanges({ summaryExpanded: !expanded });
214
+ }, [expanded, notifyFeedChanges]);
208
215
  /**
209
216
  * Render header action
210
217
  * if author = authenticated user -> render edit action
@@ -382,7 +389,7 @@ export default function FeedObject(inProps) {
382
389
  React.createElement(Typography, { component: "div", className: classes.text, variant: "body2", gutterBottom: true, dangerouslySetInnerHTML: {
383
390
  __html: summaryHtml
384
391
  } })),
385
- !expanded && summaryHtmlTruncated && (React.createElement(Button, { size: "small", variant: "text", color: "inherit", onClick: () => setExpanded(!expanded) },
392
+ !expanded && summaryHtmlTruncated && (React.createElement(Button, { size: "small", variant: "text", color: "inherit", onClick: handleToggleSummary },
386
393
  React.createElement(FormattedMessage, { id: "ui.feedObject.content.showMore", defaultMessage: "ui.feedObject.content.showMore" })))));
387
394
  }
388
395
  else if (template === SCFeedObjectTemplateType.DETAIL) {
@@ -395,7 +402,7 @@ export default function FeedObject(inProps) {
395
402
  React.createElement(Typography, { component: "span", dangerouslySetInnerHTML: {
396
403
  __html: summaryHtml
397
404
  } }),
398
- !expanded && summaryHtmlTruncated && (React.createElement(Button, { size: "small", variant: "text", color: "inherit", onClick: () => setExpanded(!expanded) },
405
+ !expanded && summaryHtmlTruncated && (React.createElement(Button, { size: "small", variant: "text", color: "inherit", onClick: handleToggleSummary },
399
406
  React.createElement(FormattedMessage, { id: "ui.feedObject.content.showMore", defaultMessage: "ui.feedObject.content.showMore" })))));
400
407
  }
401
408
  }, [obj, template, expanded]);
@@ -449,7 +456,7 @@ export default function FeedObject(inProps) {
449
456
  template === SCFeedObjectTemplateType.PREVIEW && (obj.comment_count > 0 || (feedObjectActivities && feedObjectActivities.length > 0)) && (React.createElement(Collapse, { in: expandedActivities, timeout: "auto", classes: { root: classes.activitiesSection } },
450
457
  React.createElement(CardContent, { className: classes.activitiesContent },
451
458
  React.createElement(Activities, Object.assign({ feedObject: obj, key: selectedActivities, feedObjectActivities: feedObjectActivities, activitiesType: selectedActivities, onSetSelectedActivities: handleSelectedActivities, comments: comments, CommentsObjectProps: {
452
- CommentComponentProps: Object.assign({ onDelete: handleDeleteComment }, CommentComponentProps),
459
+ CommentComponentProps: Object.assign({ onDelete: handleDeleteComment, truncateContent: true }, CommentComponentProps),
453
460
  CommentObjectSkeletonProps: CommentObjectSkeletonProps
454
461
  }, cacheStrategy: cacheStrategy }, ActivitiesProps))))),
455
462
  composerOpen && (React.createElement(Composer, { open: composerOpen, feedObject: obj, onClose: handleToggleEdit, onSuccess: handleEditSuccess, maxWidth: "sm", fullWidth: true, scroll: "body" })))) : (React.createElement(FeedObjectSkeleton, Object.assign({ template: template }, FeedObjectSkeletonProps)))));
@@ -97,9 +97,13 @@ export default function PrivateMessageEditor(inProps) {
97
97
  setMessage(event.target.value);
98
98
  };
99
99
  const handleEmojiClick = (emojiData, event) => {
100
- const cursor = ref.current.selectionStart;
101
- const text = message.slice(0, cursor) + emojiData.emoji;
102
- setMessage(text);
100
+ const cursorPosition = ref.current.selectionEnd;
101
+ const start = ref.current.value.substring(0, ref.current.selectionStart);
102
+ const end = ref.current.value.substring(ref.current.selectionStart);
103
+ setMessage(start + emojiData.emoji + end);
104
+ setTimeout(() => {
105
+ ref.current.selectionStart = ref.current.selectionEnd = cursorPosition + emojiData.emoji.length;
106
+ }, 50);
103
107
  };
104
108
  // EFFECTS
105
109
  useEffect(() => {
@@ -127,8 +131,7 @@ export default function PrivateMessageEditor(inProps) {
127
131
  return (React.createElement(React.Fragment, null, openMediaSection ? (React.createElement(MessageMediaUploader, { className: classes.uploadMediaSection, open: openMediaSection, onClose: handleMediaSectionClose, forwardMessageFile: handleMessageFiles, isUploading: setUploading, action: React.createElement(IconButton, { disabled: (!message && !messageFiles.length) || uploading, onClick: handleMessageSend },
128
132
  React.createElement(Icon, null, "send")) })) : (React.createElement(React.Fragment, null,
129
133
  openEmojiSection && React.createElement(EmojiPicker, { className: classes.emojiSection, onEmojiClick: handleEmojiClick, width: "100%", searchDisabled: true }),
130
- React.createElement(TextField, { size: "small", disabled: Boolean(messageFiles.length) || openMediaSection, ref: ref, className: classes.messageInput, multiline: true, placeholder: `${intl.formatMessage(messages.placeholder)}`, value: message, onChange: handleMessageInput, maxRows: 2, onSelect: () => setOpenEmojiSection(false), InputProps: {
131
- disableUnderline: true,
134
+ React.createElement(TextField, { size: "small", inputRef: ref, disabled: Boolean(messageFiles.length) || openMediaSection, className: classes.messageInput, multiline: true, placeholder: `${intl.formatMessage(messages.placeholder)}`, value: message, onChange: handleMessageInput, maxRows: 2, onSelect: () => setOpenEmojiSection(false), InputProps: {
132
135
  startAdornment: (React.createElement(React.Fragment, null,
133
136
  React.createElement(IconButton, { disabled: openMediaSection, onClick: () => setOpenEmojiSection(!openEmojiSection) },
134
137
  React.createElement(Icon, null, "sentiment_satisfied_alt")),