mirror of
https://github.com/element-hq/element-web
synced 2024-11-24 02:05:45 +03:00
Merge branch 'develop' into matthew/postcss
This commit is contained in:
commit
a0bbe3a306
6 changed files with 143 additions and 83 deletions
|
@ -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;}
|
||||
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 = (
|
||||
<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)}} />
|
||||
<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)}}
|
||||
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 a new issue