diff --git a/package-lock.json b/package-lock.json index 23885876fc..4a24d05ea6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@viz-js/viz": "^3.2.4", "amplitude-js": "^5.11.0", "array-move": "^2.2.2", + "audio-react-recorder": "^1.0.5", "blueimp-load-image": "^2.31.0", "copy-webpack-plugin": "^12.0.2", "d3": "^7.0.1", @@ -2702,6 +2703,20 @@ "node": ">= 4.0.0" } }, + "node_modules/audio-react-recorder": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/audio-react-recorder/-/audio-react-recorder-1.0.5.tgz", + "integrity": "sha512-pvzb0rnSDmzd5IjZNSbl3TQ2X/ZQ9vnVHfdtUCYaafrFm4jcdIzSqqM64qqqt+LXbQGVr3nMale1GH6z/yCRww==", + "dependencies": { + "prop-types": "^15.7.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", diff --git a/package.json b/package.json index c0fe915dd2..741eb8a120 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "@viz-js/viz": "^3.2.4", "amplitude-js": "^5.11.0", "array-move": "^2.2.2", + "audio-react-recorder": "^1.0.5", "blueimp-load-image": "^2.31.0", "copy-webpack-plugin": "^12.0.2", "d3": "^7.0.1", diff --git a/src/img/icon/chat/buttons/voice0.svg b/src/img/icon/chat/buttons/voice0.svg new file mode 100644 index 0000000000..b5591678f9 --- /dev/null +++ b/src/img/icon/chat/buttons/voice0.svg @@ -0,0 +1 @@ + diff --git a/src/img/icon/chat/buttons/voice1.svg b/src/img/icon/chat/buttons/voice1.svg new file mode 100644 index 0000000000..984c2e33be --- /dev/null +++ b/src/img/icon/chat/buttons/voice1.svg @@ -0,0 +1 @@ + diff --git a/src/json/constant.ts b/src/json/constant.ts index de42980878..c87370b079 100644 --- a/src/json/constant.ts +++ b/src/json/constant.ts @@ -73,7 +73,7 @@ export default { image: [ 'jpg', 'jpeg', 'png', 'gif', 'svg', 'webp' ], video: [ 'mp4', 'm4v', 'mov' ], cover: [ 'jpg', 'jpeg', 'png', 'gif', 'webp' ], - audio: [ 'mp3', 'm4a', 'flac', 'ogg', 'wav' ], + audio: [ 'mp3', 'm4a', 'flac', 'ogg', 'wav', 'wave', 'x-wav' ], pdf: [ 'pdf' ], import: { 1: [ 'zip', 'md' ], diff --git a/src/json/text.json b/src/json/text.json index 9165914303..237c16ba3f 100644 --- a/src/json/text.json +++ b/src/json/text.json @@ -684,6 +684,7 @@ "blockChatButtonObject": "Add object", "blockChatButtonEmoji": "Add emoji", "blockChatButtonMention": "Add mention", + "blockChatButtonVoice": "Record voice message", "blockChatNewMessages": "New messages", "blockChatMessageEdited": "(edited)", "blockChatEditing": "Editing message", diff --git a/src/scss/block/chat/form.scss b/src/scss/block/chat/form.scss index 93ce766704..cb28bc53b3 100644 --- a/src/scss/block/chat/form.scss +++ b/src/scss/block/chat/form.scss @@ -75,6 +75,8 @@ .icon.text { background-image: url('~img/icon/chat/buttons/text.svg'); } .icon.emoji { background-image: url('~img/icon/chat/buttons/emoji.svg'); } .icon.mention { background-image: url('~img/icon/chat/buttons/mention.svg'); } + .icon.voice { background-image: url('~img/icon/chat/buttons/voice0.svg'); background-size: 20px 20px; } + .icon.isActive.voice { background-image: url('~img/icon/chat/buttons/voice1.svg'); background-color: var(--color-red) !important; } /* Text buttons */ .icon.bold { background-image: url('~img/icon/menu/action/mark/bold0.svg'); } @@ -111,6 +113,8 @@ .inner.textColor-teal { background: var(--color-teal); } .inner.textColor-lime { background: var(--color-lime); } } + + .audio-react-recorder { position: absolute; width: 0px; height: 0px; opacity: 0; overflow: hidden; } } .icon.send { position: absolute; bottom: 10px; right: 16px; width: 20px; height: 20px; background: url('~img/icon/chat/buttons/send.svg'); } diff --git a/src/ts/component/block/chat/attachment/index.tsx b/src/ts/component/block/chat/attachment/index.tsx index e8346db830..c622fbcbef 100644 --- a/src/ts/component/block/chat/attachment/index.tsx +++ b/src/ts/component/block/chat/attachment/index.tsx @@ -52,6 +52,15 @@ const ChatAttachment = observer(class ChatAttachment extends React.Component; @@ -232,4 +242,4 @@ const ChatAttachment = observer(class ChatAttachment extends React.Component void; addAttachments: (attachments: any[], callBack?: () => void) => void; removeBookmark: (url: string) => void; + onVoice: (audio: any) => void; }; interface State { buttons: any[]; + recordState: any; }; const ChatButtons = observer(class ChatButtons extends React.Component { state = { buttons: [], + recordState: null, }; + isRecording: boolean = false; constructor (props: Props) { super(props); @@ -36,18 +41,21 @@ const ChatButtons = observer(class ChatButtons extends React.Component {buttons.map((item: any, i: number) => { const cn = [ item.icon, 'withBackground' ]; + const isRecording = (item.type == I.ChatButton.Voice) && this.isRecording; - if (item.isActive) { + if (item.isActive || isRecording) { cn.push('isActive'); }; @@ -64,6 +72,8 @@ const ChatButtons = observer(class ChatButtons extends React.Component ); })} + + ); }; @@ -109,6 +119,13 @@ const ChatButtons = observer(class ChatButtons extends React.Component { this.removeBookmark = this.removeBookmark.bind(this); this.getMarksAndRange = this.getMarksAndRange.bind(this); this.getObjectFromPath = this.getObjectFromPath.bind(this); + this.onVoice = this.onVoice.bind(this); }; render () { @@ -187,6 +188,7 @@ const ChatForm = observer(class ChatForm extends React.Component { addAttachments={this.addAttachments} onMenuClose={this.onMenuClose} removeBookmark={this.removeBookmark} + onVoice={this.onVoice} /> ) : ''} @@ -430,9 +432,7 @@ const ChatForm = observer(class ChatForm extends React.Component { const cb = e.clipboardData || e.originalEvent.clipboardData; const text = U.Common.normalizeLineEndings(String(cb.getData('text/plain') || '')); const electron = U.Common.getElectron(); - const list = U.Common.getDataTransferFiles((e.clipboardData || e.originalEvent.clipboardData).items).map((it: File) => this.getObjectFromFile(it)).filter(it => { - return !electron.isDirectory(it.path); - }); + const list = U.Common.getDataTransferFiles((e.clipboardData || e.originalEvent.clipboardData).items).map((it: File) => this.getObjectFromFile(it)); let value = this.getTextValue(); let url = U.Common.matchUrl(text); @@ -746,6 +746,22 @@ const ChatForm = observer(class ChatForm extends React.Component { }); }; + onVoice (audio) { + const { blob } = audio; + const ext = blob.type.split('/')[1]; + const file = new File([ blob ], `Voice Message.${ext}`, { type: blob.type }); + + U.Common.saveClipboardFiles([ file ], {}, (data) => { + if (!data.files || !data.files.length) { + return; + }; + + const obj = this.getObjectFromPath(data.files[0].path); + + this.addAttachments([ obj ]); + }); + }; + getMarksAndRange (): any { return { marks: this.marks, range: this.range }; }; diff --git a/src/ts/interface/block/chat.ts b/src/ts/interface/block/chat.ts index 627c2aa72f..27c6bde328 100644 --- a/src/ts/interface/block/chat.ts +++ b/src/ts/interface/block/chat.ts @@ -5,6 +5,7 @@ export enum ChatButton { Text = 1, Emoji = 2, Mention = 3, + Voice = 4, }; export enum AttachmentType { @@ -40,4 +41,4 @@ export interface ChatMessageAttachment { type: AttachmentType; }; -export interface BlockChat extends I.Block {}; \ No newline at end of file +export interface BlockChat extends I.Block {};