if (!context.accountId) { return ""; } const item = props.item; State.init({ image: {}, text: props.initialText || "", }); function extractMentions(text) { const mentionRegex = /@((?:(?:[a-z\d]+[-_])*[a-z\d]+\.)*(?:[a-z\d]+[-_])*[a-z\d]+)/gi; mentionRegex.lastIndex = 0; const accountIds = new Set(); for (const match of text.matchAll(mentionRegex)) { if ( !/[\w`]/.test(match.input.charAt(match.index - 1)) && !/[/\w`]/.test(match.input.charAt(match.index + match[0].length)) && match[1].length >= 2 && match[1].length <= 64 ) { accountIds.add(match[1].toLowerCase()); } } return [...accountIds]; } function extractTagNotifications(text, item) { return extractMentions(text || "") .filter((accountId) => accountId !== context.accountId) .map((accountId) => ({ key: accountId, value: { type: "mention", item, }, })); } function composeData() { const data = { post: { comment: JSON.stringify({ type: "md", text: state.text, image: state.image.cid ? { ipfs_cid: state.image.cid } : undefined, }), }, index: { comment: JSON.stringify({ key: item, value: { type: "md", }, }), }, }; const notifications = extractTagNotifications(state.text, { type: "social", path: `${context.accountId}/post/comment`, }); if (props.notifyAccountId && props.notifyAccountId !== context.accountId) { notifications.push({ key: props.notifyAccountId, value: { type: "comment", item, }, }); } if (notifications.length) { data.index.notify = JSON.stringify( notifications.length > 1 ? notifications : notifications[0] ); } return data; } function onCommit() { State.update({ image: {}, text: "", }); if (props.onComment) { props.onComment(); } } const Wrapper = styled.div` --padding: 12px; position: relative; border: 1px solid #ECEEF0; border-radius: 8px; margin-left: -12px; `; const Textarea = styled.div` display: grid; vertical-align: top; align-items: center; position: relative; align-items: stretch; &::after, textarea { width: 100%; min-width: 1em; height: unset; min-height: 124px; font: inherit; padding: var(--padding) var(--padding) calc(40px + (var(--padding) * 2)) var(--padding); margin: 0; resize: none; background: none; appearance: none; border: none; grid-area: 1 / 1; overflow: hidden; outline: none; } &::after { content: attr(data-value) ' '; visibility: hidden; white-space: pre-wrap; } textarea { transition: all 200ms; &:focus { box-shadow: inset 0 0 30px rgba(0,0,0,0.05); border-color: #687076; } } `; const Actions = styled.div` display: inline-flex; gap: 12px; position: absolute; bottom: var(--padding); right: var(--padding); .commit-post-button { background: #0091FF; color: #fff; border-radius: 6px; height: 40px; padding: 0 35px; font-weight: 600; font-size: 14px; border: none; cursor: pointer; transition: background 200ms, opacity 200ms; &:hover, &:focus { background: #0484e5; outline: none; } &:disabled { opacity: 0.5; pointer-events: none; } } .upload-image-button { display: flex; align-items: center; justify-content: center; background: #F1F3F5; color: #006ADC; border-radius: 6px; height: 40px; min-width: 40px; font-size: 0; border: none; cursor: pointer; transition: background 200ms, opacity 200ms; &::before { font-size: 16px; } &:hover, &:focus { background: #d7dbde; outline: none; } &:disabled { opacity: 0.5; pointer-events: none; } span { margin-left: 12px; } } .d-inline-block { display: flex !important; gap: 12px; margin: 0 !important; .overflow-hidden { width: 40px !important; height: 40px !important; } } `; return ( <Wrapper> <Textarea data-value={state.text}> <textarea placeholder="Write your reply..." onInput={(event) => State.update({ text: event.target.value })} value={state.text} /> </Textarea> <Actions> <IpfsImageUpload image={state.image} className="upload-image-button bi bi-image" /> <CommitButton disabled={!state.text} force data={composeData} onCommit={onCommit} className="commit-post-button" > Reply </CommitButton> </Actions> </Wrapper> );