Merge branch 'develop' into release-v0.12.4
This commit is contained in:
commit
e596924074
14 changed files with 65 additions and 32 deletions
|
@ -14,8 +14,13 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.mx_ReplyThread {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ReplyThread .mx_DateSeparator {
|
.mx_ReplyThread .mx_DateSeparator {
|
||||||
font-size: 1em !important;
|
font-size: 1em !important;
|
||||||
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding-bottom: 1px;
|
padding-bottom: 1px;
|
||||||
bottom: -5px;
|
bottom: -5px;
|
||||||
|
|
|
@ -116,6 +116,12 @@ export default class FromWidgetPostMessageApi {
|
||||||
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
return; // don't log this - debugging APIs like to spam postMessage which floods the log otherwise
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Although the requestId is required, we don't use it. We'll be nice and process the message
|
||||||
|
// if the property is missing, but with a warning for widget developers.
|
||||||
|
if (!event.data.requestId) {
|
||||||
|
console.warn("fromWidget action '" + event.data.action + "' does not have a requestId");
|
||||||
|
}
|
||||||
|
|
||||||
const action = event.data.action;
|
const action = event.data.action;
|
||||||
const widgetId = event.data.widgetId;
|
const widgetId = event.data.widgetId;
|
||||||
if (action === 'content_loaded') {
|
if (action === 'content_loaded') {
|
||||||
|
@ -137,12 +143,15 @@ export default class FromWidgetPostMessageApi {
|
||||||
});
|
});
|
||||||
} else if (action === 'm.sticker') {
|
} else if (action === 'm.sticker') {
|
||||||
// console.warn('Got sticker message from widget', widgetId);
|
// console.warn('Got sticker message from widget', widgetId);
|
||||||
dis.dispatch({action: 'm.sticker', data: event.data.widgetData, widgetId: event.data.widgetId});
|
// NOTE -- The widgetData field is deprecated (in favour of the 'data' field) and will be removed eventually
|
||||||
|
const data = event.data.data || event.data.widgetData;
|
||||||
|
dis.dispatch({action: 'm.sticker', data: data, widgetId: event.data.widgetId});
|
||||||
} else if (action === 'integration_manager_open') {
|
} else if (action === 'integration_manager_open') {
|
||||||
// Close the stickerpicker
|
// Close the stickerpicker
|
||||||
dis.dispatch({action: 'stickerpicker_close'});
|
dis.dispatch({action: 'stickerpicker_close'});
|
||||||
// Open the integration manager
|
// Open the integration manager
|
||||||
const data = event.data.widgetData;
|
// NOTE -- The widgetData field is deprecated (in favour of the 'data' field) and will be removed eventually
|
||||||
|
const data = event.data.data || event.data.widgetData;
|
||||||
const integType = (data && data.integType) ? data.integType : null;
|
const integType = (data && data.integType) ? data.integType : null;
|
||||||
const integId = (data && data.integId) ? data.integId : null;
|
const integId = (data && data.integId) ? data.integId : null;
|
||||||
IntegrationManager.open(integType, integId);
|
IntegrationManager.open(integType, integId);
|
||||||
|
|
|
@ -186,7 +186,6 @@ const sanitizeHtmlParams = {
|
||||||
],
|
],
|
||||||
allowedAttributes: {
|
allowedAttributes: {
|
||||||
// custom ones first:
|
// custom ones first:
|
||||||
blockquote: ['data-mx-reply'], // used to allow explicit removal of a reply fallback blockquote, value ignored
|
|
||||||
font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
|
font: ['color', 'data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
|
||||||
span: ['data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
|
span: ['data-mx-bg-color', 'data-mx-color', 'style'], // custom to matrix
|
||||||
a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix
|
a: ['href', 'name', 'target', 'rel'], // remote target: custom to matrix
|
||||||
|
|
|
@ -349,7 +349,7 @@ function setWidget(event, roomId) {
|
||||||
userWidgets[widgetId] = {
|
userWidgets[widgetId] = {
|
||||||
content: content,
|
content: content,
|
||||||
sender: client.getUserId(),
|
sender: client.getUserId(),
|
||||||
stateKey: widgetId,
|
state_key: widgetId,
|
||||||
type: 'm.widget',
|
type: 'm.widget',
|
||||||
id: widgetId,
|
id: widgetId,
|
||||||
};
|
};
|
||||||
|
|
|
@ -51,11 +51,11 @@ export default class ToWidgetPostMessageApi {
|
||||||
if (payload.response === undefined) {
|
if (payload.response === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const promise = this._requestMap[payload._id];
|
const promise = this._requestMap[payload.requestId];
|
||||||
if (!promise) {
|
if (!promise) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
delete this._requestMap[payload._id];
|
delete this._requestMap[payload.requestId];
|
||||||
promise.resolve(payload);
|
promise.resolve(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,21 +64,21 @@ export default class ToWidgetPostMessageApi {
|
||||||
targetWindow = targetWindow || window.parent; // default to parent window
|
targetWindow = targetWindow || window.parent; // default to parent window
|
||||||
targetOrigin = targetOrigin || "*";
|
targetOrigin = targetOrigin || "*";
|
||||||
this._counter += 1;
|
this._counter += 1;
|
||||||
action._id = Date.now() + "-" + Math.random().toString(36) + "-" + this._counter;
|
action.requestId = Date.now() + "-" + Math.random().toString(36) + "-" + this._counter;
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this._requestMap[action._id] = {resolve, reject};
|
this._requestMap[action.requestId] = {resolve, reject};
|
||||||
targetWindow.postMessage(action, targetOrigin);
|
targetWindow.postMessage(action, targetOrigin);
|
||||||
|
|
||||||
if (this._timeoutMs > 0) {
|
if (this._timeoutMs > 0) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!this._requestMap[action._id]) {
|
if (!this._requestMap[action.requestId]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.error("postMessage request timed out. Sent object: " + JSON.stringify(action),
|
console.error("postMessage request timed out. Sent object: " + JSON.stringify(action),
|
||||||
this._requestMap);
|
this._requestMap);
|
||||||
this._requestMap[action._id].reject(new Error("Timed out"));
|
this._requestMap[action.requestId].reject(new Error("Timed out"));
|
||||||
delete this._requestMap[action._id];
|
delete this._requestMap[action.requestId];
|
||||||
}, this._timeoutMs);
|
}, this._timeoutMs);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -44,6 +44,8 @@ export default class WidgetMessaging {
|
||||||
}
|
}
|
||||||
|
|
||||||
messageToWidget(action) {
|
messageToWidget(action) {
|
||||||
|
action.widgetId = this.widgetId; // Required to be sent for all outbound requests
|
||||||
|
|
||||||
return this.toWidget.exec(action, this.target).then((data) => {
|
return this.toWidget.exec(action, this.target).then((data) => {
|
||||||
// Check for errors and reject if found
|
// Check for errors and reject if found
|
||||||
if (data.response === undefined) { // null is valid
|
if (data.response === undefined) { // null is valid
|
||||||
|
|
|
@ -80,6 +80,7 @@ const SIMPLE_SETTINGS = [
|
||||||
{ id: "TextualBody.disableBigEmoji" },
|
{ id: "TextualBody.disableBigEmoji" },
|
||||||
{ id: "VideoView.flipVideoHorizontally" },
|
{ id: "VideoView.flipVideoHorizontally" },
|
||||||
{ id: "TagPanel.disableTagPanel" },
|
{ id: "TagPanel.disableTagPanel" },
|
||||||
|
{ id: "enableWidgetScreenshots" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// These settings must be defined in SettingsStore
|
// These settings must be defined in SettingsStore
|
||||||
|
|
|
@ -85,7 +85,7 @@ export default class AppTile extends React.Component {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the widget support a given capability
|
* Does the widget support a given capability
|
||||||
* @param {[type]} capability Capability to check for
|
* @param {string} capability Capability to check for
|
||||||
* @return {Boolean} True if capability supported
|
* @return {Boolean} True if capability supported
|
||||||
*/
|
*/
|
||||||
_hasCapability(capability) {
|
_hasCapability(capability) {
|
||||||
|
@ -281,6 +281,11 @@ export default class AppTile extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_canUserModify() {
|
_canUserModify() {
|
||||||
|
// User widgets should always be modifiable by their creator
|
||||||
|
if (this.props.userWidget && MatrixClientPeg.get().credentials.userId === this.props.creatorUserId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check if the current user can modify widgets in the current room
|
||||||
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
return WidgetUtils.canUserModifyWidgets(this.props.room.roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -598,7 +603,7 @@ export default class AppTile extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Picture snapshot - only show button when apps are maximised.
|
// Picture snapshot - only show button when apps are maximised.
|
||||||
const showPictureSnapshotButton = this._hasCapability('screenshot') && this.props.show;
|
const showPictureSnapshotButton = this._hasCapability('m.capability.screenshot') && this.props.show;
|
||||||
const showPictureSnapshotIcon = 'img/camera_green.svg';
|
const showPictureSnapshotIcon = 'img/camera_green.svg';
|
||||||
const popoutWidgetIcon = 'img/button-new-window.svg';
|
const popoutWidgetIcon = 'img/button-new-window.svg';
|
||||||
const windowStateIcon = (this.props.show ? 'img/minimize.svg' : 'img/maximize.svg');
|
const windowStateIcon = (this.props.show ? 'img/minimize.svg' : 'img/maximize.svg');
|
||||||
|
@ -702,13 +707,15 @@ AppTile.propTypes = {
|
||||||
showDelete: PropTypes.bool,
|
showDelete: PropTypes.bool,
|
||||||
// Optionally hide the popout widget icon
|
// Optionally hide the popout widget icon
|
||||||
showPopout: PropTypes.bool,
|
showPopout: PropTypes.bool,
|
||||||
// Widget apabilities to allow by default (without user confirmation)
|
// Widget capabilities to allow by default (without user confirmation)
|
||||||
// NOTE -- Use with caution. This is intended to aid better integration / UX
|
// NOTE -- Use with caution. This is intended to aid better integration / UX
|
||||||
// basic widget capabilities, e.g. injecting sticker message events.
|
// basic widget capabilities, e.g. injecting sticker message events.
|
||||||
whitelistCapabilities: PropTypes.array,
|
whitelistCapabilities: PropTypes.array,
|
||||||
// Optional function to be called on widget capability request
|
// Optional function to be called on widget capability request
|
||||||
// Called with an array of the requested capabilities
|
// Called with an array of the requested capabilities
|
||||||
onCapabilityRequest: PropTypes.func,
|
onCapabilityRequest: PropTypes.func,
|
||||||
|
// Is this an instance of a user widget
|
||||||
|
userWidget: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
AppTile.defaultProps = {
|
AppTile.defaultProps = {
|
||||||
|
@ -721,4 +728,5 @@ AppTile.defaultProps = {
|
||||||
showPopout: true,
|
showPopout: true,
|
||||||
handleMinimisePointerEvents: false,
|
handleMinimisePointerEvents: false,
|
||||||
whitelistCapabilities: [],
|
whitelistCapabilities: [],
|
||||||
|
userWidget: false,
|
||||||
};
|
};
|
||||||
|
|
|
@ -81,7 +81,7 @@ export default class ReplyThread extends React.Component {
|
||||||
|
|
||||||
// Part of Replies fallback support
|
// Part of Replies fallback support
|
||||||
static stripHTMLReply(html) {
|
static stripHTMLReply(html) {
|
||||||
return html.replace(/^<blockquote data-mx-reply>[\s\S]+?<!--end-mx-reply--><\/blockquote>/, '');
|
return html.replace(/^<mx-reply>[\s\S]+?<\/mx-reply>/, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Part of Replies fallback support
|
// Part of Replies fallback support
|
||||||
|
@ -102,8 +102,8 @@ export default class ReplyThread extends React.Component {
|
||||||
switch (ev.getContent().msgtype) {
|
switch (ev.getContent().msgtype) {
|
||||||
case 'm.text':
|
case 'm.text':
|
||||||
case 'm.notice': {
|
case 'm.notice': {
|
||||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||||
+ `<br>${html || body}<!--end-mx-reply--></blockquote>`;
|
+ `<br>${html || body}</blockquote></mx-reply>`;
|
||||||
const lines = body.trim().split('\n');
|
const lines = body.trim().split('\n');
|
||||||
if (lines.length > 0) {
|
if (lines.length > 0) {
|
||||||
lines[0] = `<${mxid}> ${lines[0]}`;
|
lines[0] = `<${mxid}> ${lines[0]}`;
|
||||||
|
@ -112,28 +112,28 @@ export default class ReplyThread extends React.Component {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'm.image':
|
case 'm.image':
|
||||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||||
+ `<br>sent an image.<!--end-mx-reply--></blockquote>`;
|
+ `<br>sent an image.</blockquote></mx-reply>`;
|
||||||
body = `> <${mxid}> sent an image.\n\n`;
|
body = `> <${mxid}> sent an image.\n\n`;
|
||||||
break;
|
break;
|
||||||
case 'm.video':
|
case 'm.video':
|
||||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||||
+ `<br>sent a video.<!--end-mx-reply--></blockquote>`;
|
+ `<br>sent a video.</blockquote></mx-reply>`;
|
||||||
body = `> <${mxid}> sent a video.\n\n`;
|
body = `> <${mxid}> sent a video.\n\n`;
|
||||||
break;
|
break;
|
||||||
case 'm.audio':
|
case 'm.audio':
|
||||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||||
+ `<br>sent an audio file.<!--end-mx-reply--></blockquote>`;
|
+ `<br>sent an audio file.</blockquote></mx-reply>`;
|
||||||
body = `> <${mxid}> sent an audio file.\n\n`;
|
body = `> <${mxid}> sent an audio file.\n\n`;
|
||||||
break;
|
break;
|
||||||
case 'm.file':
|
case 'm.file':
|
||||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> <a href="${userLink}">${mxid}</a>`
|
||||||
+ `<br>sent a file.<!--end-mx-reply--></blockquote>`;
|
+ `<br>sent a file.</blockquote></mx-reply>`;
|
||||||
body = `> <${mxid}> sent a file.\n\n`;
|
body = `> <${mxid}> sent a file.\n\n`;
|
||||||
break;
|
break;
|
||||||
case 'm.emote': {
|
case 'm.emote': {
|
||||||
html = `<blockquote data-mx-reply><a href="${evLink}">In reply to</a> * `
|
html = `<mx-reply><blockquote><a href="${evLink}">In reply to</a> * `
|
||||||
+ `<a href="${userLink}">${mxid}</a><br>${html || body}<!--end-mx-reply--></blockquote>`;
|
+ `<a href="${userLink}">${mxid}</a><br>${html || body}</blockquote></mx-reply>`;
|
||||||
const lines = body.trim().split('\n');
|
const lines = body.trim().split('\n');
|
||||||
if (lines.length > 0) {
|
if (lines.length > 0) {
|
||||||
lines[0] = `* <${mxid}> ${lines[0]}`;
|
lines[0] = `* <${mxid}> ${lines[0]}`;
|
||||||
|
|
|
@ -227,6 +227,8 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
|
const enableScreenshots = SettingsStore.getValue("enableWidgetScreenshots", this.props.room.room_id);
|
||||||
|
|
||||||
const apps = this.state.apps.map(
|
const apps = this.state.apps.map(
|
||||||
(app, index, arr) => {
|
(app, index, arr) => {
|
||||||
return (<AppTile
|
return (<AppTile
|
||||||
|
@ -242,6 +244,7 @@ module.exports = React.createClass({
|
||||||
creatorUserId={app.creatorUserId}
|
creatorUserId={app.creatorUserId}
|
||||||
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
|
widgetPageTitle={(app.data && app.data.title) ? app.data.title : ''}
|
||||||
waitForIframeLoad={app.waitForIframeLoad}
|
waitForIframeLoad={app.waitForIframeLoad}
|
||||||
|
whitelistCapabilities={enableScreenshots ? ["m.capability.screenshot"] : []}
|
||||||
/>);
|
/>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -220,8 +220,8 @@ export default class Stickerpicker extends React.Component {
|
||||||
room={this.props.room}
|
room={this.props.room}
|
||||||
type={stickerpickerWidget.content.type}
|
type={stickerpickerWidget.content.type}
|
||||||
fullWidth={true}
|
fullWidth={true}
|
||||||
userId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
|
userId={MatrixClientPeg.get().credentials.userId}
|
||||||
creatorUserId={MatrixClientPeg.get().credentials.userId}
|
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
|
||||||
waitForIframeLoad={true}
|
waitForIframeLoad={true}
|
||||||
show={true}
|
show={true}
|
||||||
showMenubar={true}
|
showMenubar={true}
|
||||||
|
@ -234,6 +234,7 @@ export default class Stickerpicker extends React.Component {
|
||||||
onMinimiseClick={this._onHideStickersClick}
|
onMinimiseClick={this._onHideStickersClick}
|
||||||
handleMinimisePointerEvents={true}
|
handleMinimisePointerEvents={true}
|
||||||
whitelistCapabilities={['m.sticker', 'visibility']}
|
whitelistCapabilities={['m.sticker', 'visibility']}
|
||||||
|
userWidget={true}
|
||||||
/>
|
/>
|
||||||
</PersistedElement>
|
</PersistedElement>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -217,6 +217,7 @@
|
||||||
"Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)",
|
"Enable URL previews for this room (only affects you)": "Enable URL previews for this room (only affects you)",
|
||||||
"Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room",
|
"Enable URL previews by default for participants in this room": "Enable URL previews by default for participants in this room",
|
||||||
"Room Colour": "Room Colour",
|
"Room Colour": "Room Colour",
|
||||||
|
"Enable widget screenshots on supported widgets": "Enable widget screenshots on supported widgets",
|
||||||
"Collecting app version information": "Collecting app version information",
|
"Collecting app version information": "Collecting app version information",
|
||||||
"Collecting logs": "Collecting logs",
|
"Collecting logs": "Collecting logs",
|
||||||
"Uploading report": "Uploading report",
|
"Uploading report": "Uploading report",
|
||||||
|
|
|
@ -265,4 +265,9 @@ export const SETTINGS = {
|
||||||
default: true,
|
default: true,
|
||||||
controller: new AudioNotificationsEnabledController(),
|
controller: new AudioNotificationsEnabledController(),
|
||||||
},
|
},
|
||||||
|
"enableWidgetScreenshots": {
|
||||||
|
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
|
||||||
|
displayName: _td('Enable widget screenshots on supported widgets'),
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -58,8 +58,7 @@ function getUserWidgetsArray() {
|
||||||
*/
|
*/
|
||||||
function getStickerpickerWidgets() {
|
function getStickerpickerWidgets() {
|
||||||
const widgets = getUserWidgetsArray();
|
const widgets = getUserWidgetsArray();
|
||||||
const stickerpickerWidgets = widgets.filter((widget) => widget.type='m.stickerpicker');
|
return widgets.filter((widget) => widget.content && widget.content.type === "m.stickerpicker");
|
||||||
return stickerpickerWidgets;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -73,7 +72,7 @@ function removeStickerpickerWidgets() {
|
||||||
}
|
}
|
||||||
const userWidgets = client.getAccountData('m.widgets').getContent() || {};
|
const userWidgets = client.getAccountData('m.widgets').getContent() || {};
|
||||||
Object.entries(userWidgets).forEach(([key, widget]) => {
|
Object.entries(userWidgets).forEach(([key, widget]) => {
|
||||||
if (widget.type === 'm.stickerpicker') {
|
if (widget.content && widget.content.type === 'm.stickerpicker') {
|
||||||
delete userWidgets[key];
|
delete userWidgets[key];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue