add UI elements for attaching images and other docs
This commit is contained in:
@@ -16,10 +16,10 @@ Background pattern from http://subtlepatterns.com/, commercial and non-commercia
|
|||||||
|
|
||||||
## Missing functionality
|
## Missing functionality
|
||||||
|
|
||||||
* Cannot send images or other media.
|
* Previews not generated for videos, audio or other docs.
|
||||||
* Cannot delete messages.
|
* Cannot delete messages.
|
||||||
* No indication of chat status (muted, blocked, etc.).
|
* No indication of some chat statuses (muted, blocked, etc.).
|
||||||
* Browser push notifications are missing.
|
* Browser push notifications are not implemented.
|
||||||
* No end-to-end encryption.
|
* No end-to-end encryption.
|
||||||
* Editing chat permissions is incomplete.
|
* Editing chat permissions is incomplete.
|
||||||
|
|
||||||
|
|||||||
+13
-1
@@ -900,7 +900,7 @@ table.permission-editor td.checkbox {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: none;
|
flex: none;
|
||||||
padding: 0.4rem;
|
padding: 0.4rem 0.4rem 0.4rem 0.6rem;
|
||||||
background-color: #eceff1;
|
background-color: #eceff1;
|
||||||
box-shadow: 0 -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
|
box-shadow: 0 -1px rgba(0,0,0,0.1); /* Faint shadow above panel */
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
@@ -911,6 +911,18 @@ table.permission-editor td.checkbox {
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#send-message-panel .material-icons {
|
||||||
|
min-width: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#send-message-panel .material-icons.secondary, .material-icons.disabled {
|
||||||
|
color: #999!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
#send-message-panel .material-icons.secondary:hover {
|
||||||
|
color: #2196f3!important;
|
||||||
|
}
|
||||||
|
|
||||||
/* A static panel shown when no topic is selected */
|
/* A static panel shown when no topic is selected */
|
||||||
#dummy-view {
|
#dummy-view {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ Sample JSON representation of the text above:
|
|||||||
LN: { name: 'a', isVoid: false },
|
LN: { name: 'a', isVoid: false },
|
||||||
MN: { name: 'a', isVoid: false },
|
MN: { name: 'a', isVoid: false },
|
||||||
HT: { name: 'a', isVoid: false },
|
HT: { name: 'a', isVoid: false },
|
||||||
XT: { name: '', isVoid: true }
|
IM: { name: 'img', isVoid: true }
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert base64-encoded string into Blob.
|
// Convert base64-encoded string into Blob.
|
||||||
@@ -127,10 +127,27 @@ Sample JSON representation of the text above:
|
|||||||
close: function(data) { return '</a>'; },
|
close: function(data) { return '</a>'; },
|
||||||
props: function(data) { return { name: data.val }; },
|
props: function(data) { return { name: data.val }; },
|
||||||
},
|
},
|
||||||
XT: {
|
IM: {
|
||||||
open: function() { return ''; },
|
open: function(data) {
|
||||||
close: function(data) { return '<img src="' + base64toObjectUrl(data.val, data.mime) + '" />'},
|
var blobUrl = base64toObjectUrl(data.val, data.mime);
|
||||||
props: function(data) { return { src: base64toObjectUrl(data.val, data.mime) }; },
|
var res = (data.name ? '<a href="' + blobUrl + '" download="' + data.name + '">' : '') +
|
||||||
|
'<img src="' + blobUrl + '"' +
|
||||||
|
(data.width ? ' width="' + data.width + '"' : '') +
|
||||||
|
(data.height ? ' height="' + data.height + '"' : '') + ' border="0" />';
|
||||||
|
console.log("open: " + res);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
close: function(data) {
|
||||||
|
return (data.name ? '</a>' : '');
|
||||||
|
},
|
||||||
|
props: function(data) {
|
||||||
|
return {
|
||||||
|
src: base64toObjectUrl(data.val, data.mime),
|
||||||
|
width: data.width,
|
||||||
|
height: data.height,
|
||||||
|
download: data.name
|
||||||
|
};
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -462,17 +479,30 @@ Sample JSON representation of the text above:
|
|||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
|
||||||
addZeroLengthMedia: function(content, at, tag, mime, previewBits, external) {
|
/**
|
||||||
|
* Add inline image to drafty content
|
||||||
|
*
|
||||||
|
* @param {Drafty} content object to add image to
|
||||||
|
* @param {integer} at index where the object is inserted
|
||||||
|
* @param {string} mime mime-type of the image, e.g. "image/png"
|
||||||
|
* @param {string} bits base64-encoded image content
|
||||||
|
* @param {integer} width width of the image
|
||||||
|
* @param {integer} height height of the image
|
||||||
|
* @param {string} fname file name suggestion for downloading the image.
|
||||||
|
*/
|
||||||
|
insertImage: function(content, at, mime, base64bits, width, height, fname) {
|
||||||
content = content || {txt: ""};
|
content = content || {txt: ""};
|
||||||
content.ent = content.ent || [];
|
content.ent = content.ent || [];
|
||||||
content.fmt = content.fmt || [];
|
content.fmt = content.fmt || [];
|
||||||
|
|
||||||
var entity = {
|
var entity = {
|
||||||
tp: tag ? tag : "XT",
|
tp: "IM",
|
||||||
data: {
|
data: {
|
||||||
mime: mime,
|
mime: mime,
|
||||||
val: previewBits,
|
val: base64bits,
|
||||||
ext: external
|
width: width,
|
||||||
|
height: height,
|
||||||
|
name: fname
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var range = {
|
var range = {
|
||||||
@@ -514,8 +544,10 @@ Sample JSON representation of the text above:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (DECORATORS[tp]) {
|
if (DECORATORS[tp]) {
|
||||||
markup.push({idx: range.at, what: DECORATORS[tp].open(data)});
|
// Because we later sort in descending order, closing markup must come first.
|
||||||
|
// Otherwise zero-length objects will not be represented correctly.
|
||||||
markup.push({idx: range.at + range.len, what: DECORATORS[tp].close(data)});
|
markup.push({idx: range.at + range.len, what: DECORATORS[tp].close(data)});
|
||||||
|
markup.push({idx: range.at, what: DECORATORS[tp].open(data)});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-6
@@ -2691,7 +2691,10 @@
|
|||||||
{this.state.writeOnly ?
|
{this.state.writeOnly ?
|
||||||
<div id="write-only-note">no access to messages</div>
|
<div id="write-only-note">no access to messages</div>
|
||||||
: null}
|
: null}
|
||||||
<MessagesView.Send topic={this.props.topic} sendMessage={this.props.sendMessage} />
|
<MessagesView.Send
|
||||||
|
topic={this.props.topic}
|
||||||
|
disabled={this.state.readOnly}
|
||||||
|
sendMessage={this.props.sendMessage} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -2713,6 +2716,12 @@
|
|||||||
// Make initial keypress time as if it happened 5001 milliseconds in the past.
|
// Make initial keypress time as if it happened 5001 milliseconds in the past.
|
||||||
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
|
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
|
||||||
};
|
};
|
||||||
|
},
|
||||||
|
handleAttachImage: function() {
|
||||||
|
|
||||||
|
},
|
||||||
|
handleAttachFile: function() {
|
||||||
|
|
||||||
},
|
},
|
||||||
handleSend: function() {
|
handleSend: function() {
|
||||||
var message = this.state.message.trim();
|
var message = this.state.message.trim();
|
||||||
@@ -2747,13 +2756,30 @@
|
|||||||
this.setState(newState);
|
this.setState(newState);
|
||||||
},
|
},
|
||||||
render: function() {
|
render: function() {
|
||||||
|
var prompt = this.props.disabled ? "Publishing disabled" : "New message";
|
||||||
|
var instance = this;
|
||||||
return (
|
return (
|
||||||
<div id="send-message-panel">
|
<div id="send-message-panel">
|
||||||
<textarea id="sendMessage" placeholder="New message"
|
{this.props.disabled ?
|
||||||
value={this.state.message} onChange={this.handleMessageTyping} onKeyPress={this.handleKeyPress} />
|
<i className="material-icons disabled">photo</i> :
|
||||||
<a href="#" onClick={this.handleSend}>
|
<a href="#" onClick={function(e) {instance.attachImage.click();}}>
|
||||||
<i className="material-icons">send</i>
|
<i className="material-icons secondary">photo</i>
|
||||||
</a>
|
</a>}
|
||||||
|
{this.props.disabled ?
|
||||||
|
<i className="material-icons disabled">attach_file</i> :
|
||||||
|
<a href="#" onClick={function(e) {instance.attachFile.click();}}>
|
||||||
|
<i className="material-icons secondary">attach_file</i>
|
||||||
|
</a>}
|
||||||
|
<textarea id="sendMessage" placeholder={prompt}
|
||||||
|
disabled={this.props.disabled} value={this.state.message}
|
||||||
|
onChange={this.handleMessageTyping} onKeyPress={this.handleKeyPress} />
|
||||||
|
{this.props.disabled ?
|
||||||
|
<i className="material-icons disabled">send</i> :
|
||||||
|
<a href="#" onClick={this.handleSend}><i className="material-icons">send</i></a>}
|
||||||
|
<input type="file" ref={function(ref) {instance.attachFile = ref;}}
|
||||||
|
onChange={this.handleAttachFile} style={{display: 'none'}} />
|
||||||
|
<input type="file" ref={function(ref) {instance.attachImage = ref;}} accept="image/*"
|
||||||
|
onChange={this.handleAttachImage} style={{display: 'none'}} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user