Merge branch 'develop' into matthew/postcss
						commit
						a0bbe3a306
					
				| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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. <br /> not <br>)
 | 
			
		||||
});
 | 
			
		||||
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; }
 | 
			
		||||
 | 
			
		||||
        // 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 '<p>' + text + '</p>';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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})`
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 = (
 | 
			
		||||
                <div>
 | 
			
		||||
                    <input type="text" ref="email"
 | 
			
		||||
                        autoFocus={true} placeholder="Email address (optional)"
 | 
			
		||||
                        defaultValue={this.props.defaultEmail}
 | 
			
		||||
                        className={this._classForField(FIELD_EMAIL, 'mx_Login_field')}
 | 
			
		||||
                    onBlur={function() {self.validateField(FIELD_EMAIL)}} />
 | 
			
		||||
                        onBlur={function() {self.validateField(FIELD_EMAIL)}}
 | 
			
		||||
                        value={self.state.email}/>
 | 
			
		||||
                    {emailSuffix ? <input className="mx_Login_field" value={emailSuffix} disabled/> : null }
 | 
			
		||||
                </div>
 | 
			
		||||
            );
 | 
			
		||||
            if (this.props.teamsConfig) {
 | 
			
		||||
                teamSection = (
 | 
			
		||||
                    <select
 | 
			
		||||
                        defaultValue="-1"
 | 
			
		||||
                        className="mx_Login_field"
 | 
			
		||||
                        onBlur={function() {self.validateField(FIELD_EMAIL)}}
 | 
			
		||||
                        onChange={function(ev) {self.onSelectTeam(ev.target.value)}}
 | 
			
		||||
                    >
 | 
			
		||||
                        <option key="-1" value="-1">No team</option>
 | 
			
		||||
                        {this.props.teamsConfig.teams.map((t, index) => {
 | 
			
		||||
                            return (
 | 
			
		||||
                                <option key={index} value={index}>
 | 
			
		||||
                                    {t.name}
 | 
			
		||||
                                </option>
 | 
			
		||||
                            );
 | 
			
		||||
                        })}
 | 
			
		||||
                        <option key="-2" value="other">Other</option>
 | 
			
		||||
                    </select>
 | 
			
		||||
                );
 | 
			
		||||
                if (this.props.teamsConfig.supportEmail && this.state.showSupportEmail) {
 | 
			
		||||
                    teamAdditionSupport = (
 | 
			
		||||
                        <span>
 | 
			
		||||
                            If your team is not listed, email 
 | 
			
		||||
                            <a href={"mailto:" + this.props.teamsConfig.supportEmail}>
 | 
			
		||||
                                {this.props.teamsConfig.supportEmail}
 | 
			
		||||
                            </a>
 | 
			
		||||
                        </span>
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (this.props.onRegisterClick) {
 | 
			
		||||
            registerButton = (
 | 
			
		||||
| 
						 | 
				
			
			@ -248,6 +329,9 @@ module.exports = React.createClass({
 | 
			
		|||
        return (
 | 
			
		||||
            <div>
 | 
			
		||||
                <form onSubmit={this.onSubmit}>
 | 
			
		||||
                    {teamSection}
 | 
			
		||||
                    {teamAdditionSupport}
 | 
			
		||||
                    <br />
 | 
			
		||||
                    {emailSection}
 | 
			
		||||
                    <br />
 | 
			
		||||
                    <input type="text" ref="username"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue