diff --git a/package.json b/package.json
index 47d6c9aced..906417a953 100644
--- a/package.json
+++ b/package.json
@@ -58,7 +58,7 @@
"isomorphic-fetch": "^2.2.1",
"linkifyjs": "^2.1.3",
"lodash": "^4.13.1",
- "marked": "^0.3.5",
+ "commonmark": "^0.27.0",
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
"optimist": "^0.6.1",
"q": "^1.4.1",
diff --git a/src/Markdown.js b/src/Markdown.js
index a7b267b110..18c888b541 100644
--- a/src/Markdown.js
+++ b/src/Markdown.js
@@ -14,20 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import marked from 'marked';
-
-// marked only applies the default options on the high
-// level marked() interface, so we do it here.
-const marked_options = Object.assign({}, marked.defaults, {
- gfm: true,
- tables: true,
- breaks: true,
- pedantic: false,
- sanitize: true,
- smartLists: true,
- smartypants: false,
- xhtml: true, // return self closing tags (ie.
not
)
-});
+import commonmark from 'commonmark';
/**
* Class that wraps marked, adding the ability to see whether
@@ -36,16 +23,7 @@ const marked_options = Object.assign({}, marked.defaults, {
*/
export default class Markdown {
constructor(input) {
- const lexer = new marked.Lexer(marked_options);
- this.tokens = lexer.lex(input);
- }
-
- _copyTokens() {
- // copy tokens (the parser modifies its input arg)
- const tokens_copy = this.tokens.slice();
- // it also has a 'links' property, because this is javascript
- // and why wouldn't you have an array that also has properties?
- return Object.assign(tokens_copy, this.tokens);
+ this.input = input
}
isPlainText() {
@@ -64,65 +42,41 @@ export default class Markdown {
is_plain = false;
}
- const dummy_renderer = {};
- for (const k of Object.keys(marked.Renderer.prototype)) {
+ const dummy_renderer = new commonmark.HtmlRenderer();
+ for (const k of Object.keys(commonmark.HtmlRenderer.prototype)) {
dummy_renderer[k] = setNotPlain;
}
// text and paragraph are just text
- dummy_renderer.text = function(t){return t;}
- dummy_renderer.paragraph = function(t){return t;}
+ dummy_renderer.text = function(t) { return t; }
+ dummy_renderer.paragraph = function(t) { return t; }
- // ignore links where text is just the url:
- // this ignores plain URLs that markdown has
- // detected whilst preserving markdown syntax links
- dummy_renderer.link = function(href, title, text) {
- if (text != href) {
- is_plain = false;
- }
- }
-
- const dummy_options = Object.assign({}, marked_options, {
- renderer: dummy_renderer,
- });
- const dummy_parser = new marked.Parser(dummy_options);
- dummy_parser.parse(this._copyTokens());
+ const dummy_parser = new commonmark.Parser();
+ dummy_renderer.render(dummy_parser.parse(this.input));
return is_plain;
}
toHTML() {
- const real_renderer = new marked.Renderer();
- real_renderer.link = function(href, title, text) {
- // prevent marked from turning plain URLs
- // into links, because its algorithm is fairly
- // poor. Let's send plain URLs rather than
- // badly linkified ones (the linkifier Vector
- // uses on message display is way better, eg.
- // handles URLs with closing parens at the end).
- if (text == href) {
- return href;
- }
- return marked.Renderer.prototype.link.apply(this, arguments);
- }
+ const parser = new commonmark.Parser();
- real_renderer.paragraph = (text) => {
- // The tokens at the top level are the 'blocks', so if we
- // have more than one, there are multiple 'paragraphs'.
- // If there is only one top level token, just return the
+ const renderer = new commonmark.HtmlRenderer({safe: true});
+ const real_paragraph = renderer.paragraph;
+ renderer.paragraph = function(node, entering) {
+ // If there is only one top level node, just return the
// bare text: it's a single line of text and so should be
- // 'inline', rather than necessarily wrapped in its own
- // p tag. If, however, we have multiple tokens, each gets
+ // 'inline', rather than unnecessarily wrapped in its own
+ // p tag. If, however, we have multiple nodes, each gets
// its own p tag to keep them as separate paragraphs.
- if (this.tokens.length == 1) {
- return text;
+ var par = node;
+ while (par.parent) {
+ par = par.parent
+ }
+ if (par.firstChild != par.lastChild) {
+ real_paragraph.call(this, node, entering);
}
- return '
' + text + '
'; } - const real_options = Object.assign({}, marked_options, { - renderer: real_renderer, - }); - const real_parser = new marked.Parser(real_options); - return real_parser.parse(this._copyTokens()); + var parsed = parser.parse(this.input); + return renderer.render(parsed); } } diff --git a/src/Signup.js b/src/Signup.js index f148ac2419..d3643bd749 100644 --- a/src/Signup.js +++ b/src/Signup.js @@ -203,7 +203,17 @@ class Register extends Signup { } else if (error.errcode == 'M_INVALID_USERNAME') { throw new Error("User names may only contain alphanumeric characters, underscores or dots!"); } else if (error.httpStatus >= 400 && error.httpStatus < 500) { - throw new Error(`Registration failed! (${error.httpStatus})`); + let msg = null; + if (error.message) { + msg = error.message; + } else if (error.errcode) { + msg = error.errcode; + } + if (msg) { + throw new Error(`Registration failed! (${error.httpStatus}) - ${msg}`); + } else { + throw new Error(`Registration failed! (${error.httpStatus}) - That's all we know.`); + } } else if (error.httpStatus >= 500 && error.httpStatus < 600) { throw new Error( `Server error during registration! (${error.httpStatus})` diff --git a/src/components/structures/MatrixChat.js b/src/components/structures/MatrixChat.js index 79e93bb990..7d74e2ee02 100644 --- a/src/components/structures/MatrixChat.js +++ b/src/components/structures/MatrixChat.js @@ -1045,6 +1045,7 @@ module.exports = React.createClass({ defaultHsUrl={this.getDefaultHsUrl()} defaultIsUrl={this.getDefaultIsUrl()} brand={this.props.config.brand} + teamsConfig={this.props.config.teamsConfig} customHsUrl={this.getCurrentHsUrl()} customIsUrl={this.getCurrentIsUrl()} registrationUrl={this.props.registrationUrl} diff --git a/src/components/structures/login/Registration.js b/src/components/structures/login/Registration.js index 269aabed9b..fb24b61504 100644 --- a/src/components/structures/login/Registration.js +++ b/src/components/structures/login/Registration.js @@ -49,6 +49,16 @@ module.exports = React.createClass({ email: React.PropTypes.string, username: React.PropTypes.string, guestAccessToken: React.PropTypes.string, + teamsConfig: React.PropTypes.shape({ + // Email address to request new teams + supportEmail: React.PropTypes.string, + teams: React.PropTypes.arrayOf(React.PropTypes.shape({ + // The displayed name of the team + "name": React.PropTypes.string, + // The suffix with which every team email address ends + "emailSuffix": React.PropTypes.string, + })).required, + }), defaultDeviceDisplayName: React.PropTypes.string, @@ -254,6 +264,7 @@ module.exports = React.createClass({ defaultUsername={this.state.formVals.username} defaultEmail={this.state.formVals.email} defaultPassword={this.state.formVals.password} + teamsConfig={this.props.teamsConfig} guestUsername={this.props.username} minPasswordLength={MIN_PASSWORD_LENGTH} onError={this.onFormValidationFailed} diff --git a/src/components/views/login/RegistrationForm.js b/src/components/views/login/RegistrationForm.js index 33809fbfd6..3e07302a91 100644 --- a/src/components/views/login/RegistrationForm.js +++ b/src/components/views/login/RegistrationForm.js @@ -38,6 +38,16 @@ module.exports = React.createClass({ defaultEmail: React.PropTypes.string, defaultUsername: React.PropTypes.string, defaultPassword: React.PropTypes.string, + teamsConfig: React.PropTypes.shape({ + // Email address to request new teams + supportEmail: React.PropTypes.string, + teams: React.PropTypes.arrayOf(React.PropTypes.shape({ + // The displayed name of the team + "name": React.PropTypes.string, + // The suffix with which every team email address ends + "emailSuffix": React.PropTypes.string, + })).required, + }), // A username that will be used if no username is entered. // Specifying this param will also warn the user that entering @@ -62,7 +72,8 @@ module.exports = React.createClass({ getInitialState: function() { return { - fieldValid: {} + fieldValid: {}, + selectedTeam: null, }; }, @@ -119,6 +130,25 @@ module.exports = React.createClass({ } }, + onSelectTeam: function(teamIndex) { + let team = this._getSelectedTeam(teamIndex); + if (team) { + this.refs.email.value = this.refs.email.value.split("@")[0]; + } + this.setState({ + selectedTeam: team, + showSupportEmail: teamIndex === "other", + }); + }, + + _getSelectedTeam: function(teamIndex) { + if (this.props.teamsConfig && + this.props.teamsConfig.teams[teamIndex]) { + return this.props.teamsConfig.teams[teamIndex]; + } + return null; + }, + /** * Returns true if all fields were valid last time * they were validated. @@ -139,11 +169,15 @@ module.exports = React.createClass({ switch (field_id) { case FIELD_EMAIL: - this.markFieldValid( - field_id, - this.refs.email.value == '' || Email.looksValid(this.refs.email.value), - "RegistrationForm.ERR_EMAIL_INVALID" - ); + let email = this.refs.email.value; + if (this.props.teamsConfig) { + let team = this.state.selectedTeam; + if (team) { + email = email + "@" + team.emailSuffix; + } + } + let valid = email === '' || Email.looksValid(email); + this.markFieldValid(field_id, valid, "RegistrationForm.ERR_EMAIL_INVALID"); break; case FIELD_USERNAME: // XXX: SPEC-1 @@ -222,17 +256,64 @@ module.exports = React.createClass({ return cls; }, + _renderEmailInputSuffix: function() { + let suffix = null; + if (!this.state.selectedTeam) { + return suffix; + } + let team = this.state.selectedTeam; + if (team) { + suffix = "@" + team.emailSuffix; + } + return suffix; + }, + render: function() { var self = this; - var emailSection, registerButton; + var emailSection, teamSection, teamAdditionSupport, registerButton; if (this.props.showEmail) { + let emailSuffix = this._renderEmailInputSuffix(); emailSection = ( - +