summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/react/components/access_history_modal.jsx57
-rw-r--r--web/react/components/activity_log_modal.jsx113
-rw-r--r--web/react/components/channel_header.jsx2
-rw-r--r--web/react/components/create_comment.jsx29
-rw-r--r--web/react/components/create_post.jsx29
-rw-r--r--web/react/components/file_attachment.jsx10
-rw-r--r--web/react/components/file_upload.jsx101
-rw-r--r--web/react/components/file_upload_overlay.jsx26
-rw-r--r--web/react/components/login.jsx2
-rw-r--r--web/react/components/post.jsx6
-rw-r--r--web/react/components/post_body.jsx78
-rw-r--r--web/react/components/post_list.jsx17
-rw-r--r--web/react/components/post_right.jsx34
-rw-r--r--web/react/components/search_results.jsx12
-rw-r--r--web/react/components/setting_item_min.jsx19
-rw-r--r--web/react/components/sidebar.jsx17
-rw-r--r--web/react/components/sidebar_header.jsx2
-rw-r--r--web/react/components/sidebar_right_menu.jsx2
-rw-r--r--web/react/components/user_settings.jsx41
-rw-r--r--web/react/components/user_settings_modal.jsx3
-rw-r--r--web/react/pages/channel.jsx7
-rw-r--r--web/react/stores/team_store.jsx143
-rw-r--r--web/react/utils/utils.jsx318
-rw-r--r--web/sass-files/sass/partials/_base.scss1
-rw-r--r--web/sass-files/sass/partials/_files.scss24
-rw-r--r--web/sass-files/sass/partials/_post.scss34
-rw-r--r--web/sass-files/sass/partials/_post_right.scss13
-rw-r--r--web/sass-files/sass/partials/_responsive.scss9
-rw-r--r--web/sass-files/sass/partials/_settings.scss6
-rw-r--r--web/sass-files/sass/partials/_sidebar--left.scss1
-rw-r--r--web/static/config/config.js1
-rw-r--r--web/static/js/jquery-dragster/LICENSE (renamed from web/static/js/marked/LICENSE)12
-rw-r--r--web/static/js/jquery-dragster/README.md17
-rw-r--r--web/static/js/jquery-dragster/jquery.dragster.js85
-rw-r--r--web/static/js/marked/Makefile12
-rw-r--r--web/static/js/marked/README.md406
-rwxr-xr-xweb/static/js/marked/bin/marked187
-rw-r--r--web/static/js/marked/lib/marked.js980
-rw-r--r--web/static/js/marked/man/marked.191
-rw-r--r--web/static/js/marked/marked.min.js6
-rw-r--r--web/templates/channel.html1
-rw-r--r--web/templates/head.html2
42 files changed, 677 insertions, 2279 deletions
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index 16768a119..a19e5c16e 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -13,21 +13,23 @@ function getStateFromStoresForAudits() {
}
module.exports = React.createClass({
+ displayName: 'AccessHistoryModal',
componentDidMount: function() {
- UserStore.addAuditsChangeListener(this._onChange);
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) {
+ UserStore.addAuditsChangeListener(this.onListenerChange);
+ $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function() {
AsyncClient.getAudits();
});
var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
+ $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function() {
+ $('#user_settings').modal('show');
self.setState({moreInfo: []});
});
},
componentWillUnmount: function() {
- UserStore.removeAuditsChangeListener(this._onChange);
+ UserStore.removeAuditsChangeListener(this.onListenerChange);
},
- _onChange: function() {
+ onListenerChange: function() {
var newState = getStateFromStoresForAudits();
if (!utils.areStatesEqual(newState.audits, this.state.audits)) {
this.setState(newState);
@@ -61,6 +63,21 @@ module.exports = React.createClass({
currentAudit.session_id = 'N/A (Login attempt)';
}
+ var moreInfo = (<a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
+ if (this.state.moreInfo[i]) {
+ moreInfo = (
+ <div>
+ <div>{'Session ID: ' + currentAudit.session_id}</div>
+ <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div>
+ </div>
+ );
+ }
+
+ var divider = null;
+ if (i < this.state.audits.length - 1) {
+ divider = (<div className='divider-light'></div>)
+ }
+
accessList[i] = (
<div className='access-history__table'>
<div className='access__date'>{newDate}</div>
@@ -68,25 +85,21 @@ module.exports = React.createClass({
<div className='report__time'>{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'})}</div>
<div className='report__info'>
<div>{'IP: ' + currentAudit.ip_address}</div>
- {this.state.moreInfo[i] ?
- <div>
- <div>{'Session ID: ' + currentAudit.session_id}</div>
- <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div>
- </div>
- :
- <a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>
- }
+ {moreInfo}
</div>
- {i < this.state.audits.length - 1 ?
- <div className='divider-light'/>
- :
- null
- }
+ {divider}
</div>
</div>
);
}
+ var content;
+ if (this.state.audits.loading) {
+ content = (<LoadingScreen />);
+ } else {
+ content = (<form role='form'>{accessList}</form>);
+ }
+
return (
<div>
<div className='modal fade' ref='modal' id='access-history' tabIndex='-1' role='dialog' aria-hidden='true'>
@@ -97,13 +110,7 @@ module.exports = React.createClass({
<h4 className='modal-title' id='myModalLabel'>Access History</h4>
</div>
<div ref='modalBody' className='modal-body'>
- {!this.state.audits.loading ?
- <form role='form'>
- {accessList}
- </form>
- :
- <LoadingScreen />
- }
+ {content}
</div>
</div>
</div>
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index f28f0d5f1..1192a72bc 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -10,40 +10,41 @@ var utils = require('../utils/utils.jsx');
function getStateFromStoresForSessions() {
return {
sessions: UserStore.getSessions(),
- server_error: null,
- client_error: null
+ serverError: null,
+ clientError: null
};
}
module.exports = React.createClass({
+ displayName: 'ActivityLogModal',
submitRevoke: function(altId) {
- var self = this;
Client.revokeSession(altId,
function(data) {
AsyncClient.getSessions();
}.bind(this),
function(err) {
- state = getStateFromStoresForSessions();
- state.server_error = err;
+ var state = getStateFromStoresForSessions();
+ state.serverError = err;
this.setState(state);
}.bind(this)
);
},
componentDidMount: function() {
- UserStore.addSessionsChangeListener(this._onChange);
+ UserStore.addSessionsChangeListener(this.onListenerChange);
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) {
AsyncClient.getSessions();
});
var self = this;
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({ moreInfo: [] });
+ $('#user_settings').modal('show');
+ self.setState({moreInfo: []});
});
},
componentWillUnmount: function() {
- UserStore.removeSessionsChangeListener(this._onChange);
+ UserStore.removeSessionsChangeListener(this.onListenerChange);
},
- _onChange: function() {
+ onListenerChange: function() {
var newState = getStateFromStoresForSessions();
if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) {
this.setState(newState);
@@ -52,7 +53,7 @@ module.exports = React.createClass({
handleMoreInfo: function(index) {
var newMoreInfo = this.state.moreInfo;
newMoreInfo[index] = true;
- this.setState({ moreInfo: newMoreInfo });
+ this.setState({moreInfo: newMoreInfo});
},
getInitialState: function() {
var initialState = getStateFromStoresForSessions();
@@ -61,68 +62,76 @@ module.exports = React.createClass({
},
render: function() {
var activityList = [];
- var server_error = this.state.server_error ? this.state.server_error : null;
+ var serverError = this.state.serverError;
+
+ // Squash any false-y value for server error into null
+ if (!serverError) {
+ serverError = null;
+ }
for (var i = 0; i < this.state.sessions.length; i++) {
var currentSession = this.state.sessions[i];
var lastAccessTime = new Date(currentSession.last_activity_at);
var firstAccessTime = new Date(currentSession.create_at);
- var devicePicture = "";
+ var devicePicture = '';
- if (currentSession.props.platform === "Windows") {
- devicePicture = "fa fa-windows";
+ if (currentSession.props.platform === 'Windows') {
+ devicePicture = 'fa fa-windows';
}
- else if (currentSession.props.platform === "Macintosh" || currentSession.props.platform === "iPhone") {
- devicePicture = "fa fa-apple";
+ else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
+ devicePicture = 'fa fa-apple';
}
- else if (currentSession.props.platform === "Linux") {
- devicePicture = "fa fa-linux";
+ else if (currentSession.props.platform === 'Linux') {
+ devicePicture = 'fa fa-linux';
+ }
+
+ var moreInfo;
+ if (this.state.moreInfo[i]) {
+ moreInfo = (
+ <div>
+ <div>{'First time active: ' + firstAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
+ <div>{'OS: ' + currentSession.props.os}</div>
+ <div>{'Browser: ' + currentSession.props.browser}</div>
+ <div>{'Session ID: ' + currentSession.alt_id}</div>
+ </div>
+ );
+ } else {
+ moreInfo = (<a className='theme' href='#' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
}
activityList[i] = (
- <div className="activity-log__table">
- <div className="activity-log__report">
- <div className="report__platform"><i className={devicePicture} />{currentSession.props.platform}</div>
- <div className="report__info">
- <div>{"Last activity: " + lastAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}</div>
- { this.state.moreInfo[i] ?
- <div>
- <div>{"First time active: " + firstAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}</div>
- <div>{"OS: " + currentSession.props.os}</div>
- <div>{"Browser: " + currentSession.props.browser}</div>
- <div>{"Session ID: " + currentSession.alt_id}</div>
- </div>
- :
- <a className="theme" href="#" onClick={this.handleMoreInfo.bind(this, i)}>More info</a>
- }
+ <div className='activity-log__table'>
+ <div className='activity-log__report'>
+ <div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
+ <div className='report__info'>
+ <div>{'Last activity: ' + lastAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
+ {moreInfo}
</div>
</div>
- <div className="activity-log__action"><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className="btn btn-primary">Logout</button></div>
+ <div className='activity-log__action'><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className='btn btn-primary'>Logout</button></div>
</div>
);
}
+ var content;
+ if (this.state.sessions.loading) {
+ content = (<LoadingScreen />);
+ } else {
+ content = (<form role='form'>{activityList}</form>);
+ }
+
return (
<div>
- <div className="modal fade" ref="modal" id="activity-log" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog modal-lg">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" id="myModalLabel">Active Sessions</h4>
+ <div className='modal fade' ref='modal' id='activity-log' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div className='modal-dialog modal-lg'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
+ <h4 className='modal-title' id='myModalLabel'>Active Sessions</h4>
</div>
- <p className="session-help-text">Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the "Logout" button below to end a session.</p>
- <div ref="modalBody" className="modal-body">
- { !this.state.sessions.loading ?
- <div>
- <form role="form">
- { activityList }
- </form>
- { server_error }
- </div>
- :
- <LoadingScreen />
- }
+ <p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p>
+ <div ref='modalBody' className='modal-body'>
+ {content}
</div>
</div>
</div>
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 4d64e2b94..90a776791 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -156,7 +156,7 @@ module.exports = React.createClass({
}
var channel = this.state.channel;
- var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true, noTextFormatting: true});
+ var description = utils.textToJsx(channel.description, {singleline: true, noMentionHighlight: true});
var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
var channelTitle = channel.display_name;
var currentId = UserStore.getCurrentId();
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx
index a0a018025..885efab7a 100644
--- a/web/react/components/create_comment.jsx
+++ b/web/react/components/create_comment.jsx
@@ -122,16 +122,20 @@ module.exports = React.createClass({
this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
},
handleUploadError: function(err, clientId) {
- var draft = PostStore.getCommentDraft(this.props.rootId);
+ if (clientId !== -1) {
+ var draft = PostStore.getCommentDraft(this.props.rootId);
- var index = draft['uploadsInProgress'].indexOf(clientId);
- if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
- }
+ var index = draft['uploadsInProgress'].indexOf(clientId);
+ if (index !== -1) {
+ draft['uploadsInProgress'].splice(index, 1);
+ }
- PostStore.storeCommentDraft(this.props.rootId, draft);
+ PostStore.storeCommentDraft(this.props.rootId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ } else {
+ this.setState({serverError: err});
+ }
},
clearPreviews: function() {
this.setState({previews: []});
@@ -184,7 +188,6 @@ module.exports = React.createClass({
</div>
);
}
- var allowTextFormatting = config.AllowTextFormatting;
var postError = null;
if (this.state.postError) {
@@ -205,10 +208,6 @@ module.exports = React.createClass({
if (postError) {
postFooterClassName += ' has-error';
}
- var extraInfo = <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} />;
- if (this.state.messageText.split(' ').length > 1 && allowTextFormatting) {
- extraInfo = <span className='msg-format-help'>_<em>italics</em>_ *<strong>bold</strong>* `<code className='code-info'>code</code>`</span>;
- }
return (
<form onSubmit={this.handleSubmit}>
@@ -227,9 +226,11 @@ module.exports = React.createClass({
getFileCount={this.getFileCount}
onUploadStart={this.handleUploadStart}
onFileUpload={this.handleFileUploadComplete}
- onUploadError={this.handleUploadError} />
+ onUploadError={this.handleUploadError}
+ postType='comment'
+ channelId={this.props.channelId} />
</div>
- {extraInfo}
+ <MsgTyping channelId={this.props.channelId} parentId={this.props.rootId} />
<div className={postFooterClassName}>
<input type='button' className='btn btn-primary comment-btn pull-right' value='Add Comment' onClick={this.handleSubmit} />
{postError}
diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx
index 3e1faba7d..377e7bd34 100644
--- a/web/react/components/create_post.jsx
+++ b/web/react/components/create_post.jsx
@@ -145,16 +145,20 @@ module.exports = React.createClass({
this.setState({uploadsInProgress: draft['uploadsInProgress'], previews: draft['previews']});
},
handleUploadError: function(err, clientId) {
- var draft = PostStore.getDraft(this.state.channelId);
+ if (clientId !== -1) {
+ var draft = PostStore.getDraft(this.state.channelId);
- var index = draft['uploadsInProgress'].indexOf(clientId);
- if (index !== -1) {
- draft['uploadsInProgress'].splice(index, 1);
- }
+ var index = draft['uploadsInProgress'].indexOf(clientId);
+ if (index !== -1) {
+ draft['uploadsInProgress'].splice(index, 1);
+ }
- PostStore.storeDraft(this.state.channelId, draft);
+ PostStore.storeDraft(this.state.channelId, draft);
- this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ this.setState({uploadsInProgress: draft['uploadsInProgress'], serverError: err});
+ } else {
+ this.setState({serverError: err});
+ }
},
removePreview: function(id) {
var previews = this.state.previews;
@@ -224,7 +228,6 @@ module.exports = React.createClass({
</div>
);
}
- var allowTextFormatting = config.AllowTextFormatting;
var postError = null;
if (this.state.postError) {
@@ -245,10 +248,6 @@ module.exports = React.createClass({
if (postError) {
postFooterClassName += ' has-error';
}
- var extraInfo = <MsgTyping channelId={this.state.channel_id} parentId='' />;
- if (this.state.messageText.split(' ').length > 1 && allowTextFormatting) {
- extraInfo = <span className='msg-typing'>_<em>italics</em>_ *<strong>bold</strong>* `<code className='code-info'>code</code>`</span>;
- }
return (
<form id='create_post' ref='topDiv' role='form' onSubmit={this.handleSubmit}>
@@ -267,13 +266,15 @@ module.exports = React.createClass({
getFileCount={this.getFileCount}
onUploadStart={this.handleUploadStart}
onFileUpload={this.handleFileUploadComplete}
- onUploadError={this.handleUploadError} />
+ onUploadError={this.handleUploadError}
+ postType='post'
+ channelId='' />
</div>
<div className={postFooterClassName}>
{postError}
{serverError}
{preview}
- {extraInfo}
+ <MsgTyping channelId={this.state.channelId} parentId=''/>
</div>
</div>
</form>
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index b7ea5734f..c36c908d2 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -113,6 +113,14 @@ module.exports = React.createClass({
fileSizeString = utils.fileSizeToString(this.state.fileSize);
}
+ var filenameString = decodeURIComponent(utils.getFileName(filename));
+ var trimmedFilename;
+ if (filenameString.length > 35) {
+ trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + "...";
+ } else {
+ trimmedFilename = filenameString;
+ }
+
return (
<div className="post-image__column" key={filename}>
<a className="post-image__thumbnail" href="#" onClick={this.props.handleImageClick}
@@ -120,7 +128,7 @@ module.exports = React.createClass({
{thumbnail}
</a>
<div className="post-image__details">
- <div className="post-image__name">{decodeURIComponent(utils.getFileName(filename))}</div>
+ <div data-toggle="tooltip" title={filenameString} className="post-image__name">{trimmedFilename}</div>
<div>
<span className="post-image__type">{fileInfo.ext.toUpperCase()}</span>
<span className="post-image__size">{fileSizeString}</span>
diff --git a/web/react/components/file_upload.jsx b/web/react/components/file_upload.jsx
index c1fab669c..7497ec330 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -12,7 +12,9 @@ module.exports = React.createClass({
onUploadError: React.PropTypes.func,
getFileCount: React.PropTypes.func,
onFileUpload: React.PropTypes.func,
- onUploadStart: React.PropTypes.func
+ onUploadStart: React.PropTypes.func,
+ channelId: React.PropTypes.string,
+ postType: React.PropTypes.string
},
getInitialState: function() {
return {requests: {}};
@@ -21,7 +23,7 @@ module.exports = React.createClass({
var element = $(this.refs.fileInput.getDOMNode());
var files = element.prop('files');
- var channelId = ChannelStore.getCurrentId();
+ var channelId = this.props.channelId || ChannelStore.getCurrentId();
this.props.onUploadError(null);
@@ -61,8 +63,8 @@ module.exports = React.createClass({
this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
var requests = this.state.requests;
- for (var i = 0; i < parsedData.client_ids.length; i++) {
- delete requests[parsedData.client_ids[i]];
+ for (var j = 0; j < parsedData.client_ids.length; j++) {
+ delete requests[parsedData.client_ids[j]];
}
this.setState({requests: requests});
}.bind(this),
@@ -87,10 +89,94 @@ module.exports = React.createClass({
}
} catch(e) {}
},
+ handleDrop: function(e) {
+ this.props.onUploadError(null);
+
+ var files = e.originalEvent.dataTransfer.files;
+ var channelId = this.props.channelId || ChannelStore.getCurrentId();
+
+ if (typeof files !== 'string' && files.length) {
+ var numFiles = files.length;
+
+ var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - this.props.getFileCount(channelId), numFiles);
+
+ if (numFiles > numToUpload) {
+ this.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.');
+ }
+
+ for (var i = 0; i < files.length && i < numToUpload; i++) {
+ if (files[i].size > Constants.MAX_FILE_SIZE) {
+ this.props.onUploadError('Files must be no more than ' + Constants.MAX_FILE_SIZE / 1000000 + ' MB');
+ continue;
+ }
+
+ // generate a unique id that can be used by other components to refer back to this file upload
+ var clientId = utils.generateId();
+
+ // Prepare data to be uploaded.
+ var formData = new FormData();
+ formData.append('channel_id', channelId);
+ formData.append('files', files[i], files[i].name);
+ formData.append('client_ids', clientId);
+
+ var request = client.uploadFile(formData,
+ function(data) {
+ var parsedData = $.parseJSON(data);
+ this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
+
+ var requests = this.state.requests;
+ for (var j = 0; j < parsedData.client_ids.length; j++) {
+ delete requests[parsedData.client_ids[j]];
+ }
+ this.setState({requests: requests});
+ }.bind(this),
+ function(err) {
+ this.props.onUploadError(err, clientId);
+ }.bind(this)
+ );
+
+ var requests = this.state.requests;
+ requests[clientId] = request;
+ this.setState({requests: requests});
+
+ this.props.onUploadStart([clientId], channelId);
+ }
+ } else {
+ this.props.onUploadError('Invalid file upload', -1);
+ }
+ },
componentDidMount: function() {
var inputDiv = this.refs.input.getDOMNode();
var self = this;
+ if (this.props.postType === 'post') {
+ $('.row.main').dragster({
+ enter: function() {
+ $('.center-file-overlay').removeClass('hidden');
+ },
+ leave: function() {
+ $('.center-file-overlay').addClass('hidden');
+ },
+ drop: function(dragsterEvent, e) {
+ $('.center-file-overlay').addClass('hidden');
+ self.handleDrop(e);
+ }
+ });
+ } else if (this.props.postType === 'comment') {
+ $('.post-right__container').dragster({
+ enter: function() {
+ $('.right-file-overlay').removeClass('hidden');
+ },
+ leave: function() {
+ $('.right-file-overlay').addClass('hidden');
+ },
+ drop: function(dragsterEvent, e) {
+ $('.right-file-overlay').addClass('hidden');
+ self.handleDrop(e);
+ }
+ });
+ }
+
document.addEventListener('paste', function(e) {
var textarea = $(inputDiv.parentNode.parentNode).find('.custom-textarea')[0];
@@ -133,14 +219,13 @@ module.exports = React.createClass({
continue;
}
- var channelId = ChannelStore.getCurrentId();
+ var channelId = this.props.channelId || ChannelStore.getCurrentId();
// generate a unique id that can be used by other components to refer back to this file upload
var clientId = utils.generateId();
var formData = new FormData();
formData.append('channel_id', channelId);
-
var d = new Date();
var hour;
if (d.getHours() < 10) {
@@ -165,8 +250,8 @@ module.exports = React.createClass({
self.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
var requests = self.state.requests;
- for (var i = 0; i < parsedData.client_ids.length; i++) {
- delete requests[parsedData.client_ids[i]];
+ for (var j = 0; j < parsedData.client_ids.length; j++) {
+ delete requests[parsedData.client_ids[j]];
}
self.setState({requests: requests});
},
diff --git a/web/react/components/file_upload_overlay.jsx b/web/react/components/file_upload_overlay.jsx
new file mode 100644
index 000000000..f35556371
--- /dev/null
+++ b/web/react/components/file_upload_overlay.jsx
@@ -0,0 +1,26 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+module.exports = React.createClass({
+ displayName: 'FileUploadOverlay',
+ propTypes: {
+ overlayType: React.PropTypes.string
+ },
+ render: function() {
+ var overlayClass = 'file-overlay hidden';
+ if (this.props.overlayType === 'right') {
+ overlayClass += ' right-file-overlay';
+ } else if (this.props.overlayType === 'center') {
+ overlayClass += ' center-file-overlay';
+ }
+
+ return (
+ <div className={overlayClass}>
+ <div>
+ <i className='fa fa-upload'></i>
+ <span>Drop a file to upload it.</span>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index c395bb500..f9eacf094 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -49,7 +49,7 @@ module.exports = React.createClass({
var redirect = utils.getUrlParameter('redirect');
if (redirect) {
- window.location.pathname = decodeURI(redirect);
+ window.location.pathname = decodeURIComponent(redirect);
} else {
window.location.pathname = '/' + name + '/channels/town-square';
}
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index e72a2d001..f099c67ab 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -11,12 +11,6 @@ var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({
displayName: "Post",
- componentDidMount: function() {
- $('.modal').on('show.bs.modal', function () {
- $('.modal-body').css('overflow-y', 'auto');
- $('.modal-body').css('max-height', $(window).height() * 0.7);
- });
- },
handleCommentClick: function(e) {
e.preventDefault();
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index fab6833e6..860c96d84 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -4,69 +4,59 @@
var FileAttachmentList = require('./file_attachment_list.jsx');
var UserStore = require('../stores/user_store.jsx');
var utils = require('../utils/utils.jsx');
-var formatText = require('../../static/js/marked/lib/marked.js');
module.exports = React.createClass({
componentWillReceiveProps: function(nextProps) {
var linkData = utils.extractLinks(nextProps.post.message);
- this.setState({links: linkData.links, message: linkData.text});
+ this.setState({ links: linkData["links"], message: linkData["text"] });
},
getInitialState: function() {
var linkData = utils.extractLinks(this.props.post.message);
- return {links: linkData.links, message: linkData.text};
+ return { links: linkData["links"], message: linkData["text"] };
},
render: function() {
var post = this.props.post;
var filenames = this.props.post.filenames;
var parentPost = this.props.parentPost;
var inner = utils.textToJsx(this.state.message);
- var allowTextFormatting = config.AllowTextFormatting;
- var comment = '';
- var postClass = '';
+ var comment = "";
+ var reply = "";
+ var postClass = "";
if (parentPost) {
var profile = UserStore.getProfile(parentPost.user_id);
- var apostrophe = '';
- var name = '...';
+ var apostrophe = "";
+ var name = "...";
if (profile != null) {
if (profile.username.slice(-1) === 's') {
apostrophe = "'";
} else {
apostrophe = "'s";
}
- name = <a className='theme' onClick={utils.searchForTerm.bind(this, profile.username)}>{profile.username}</a>;
+ name = <a className="theme" onClick={function(){ utils.searchForTerm(profile.username); }}>{profile.username}</a>;
}
- var message = '';
- if (parentPost.message) {
- message = utils.replaceHtmlEntities(parentPost.message);
+ var message = ""
+ if(parentPost.message) {
+ message = utils.replaceHtmlEntities(parentPost.message)
} else if (parentPost.filenames.length) {
message = parentPost.filenames[0].split('/').pop();
if (parentPost.filenames.length === 2) {
- message += ' plus 1 other file';
+ message += " plus 1 other file";
} else if (parentPost.filenames.length > 2) {
- message += ' plus ' + (parentPost.filenames.length - 1) + ' other files';
+ message += " plus " + (parentPost.filenames.length - 1) + " other files";
}
}
- if (allowTextFormatting) {
- message = formatText(message, {sanitize: true, mangle: false, gfm: true, breaks: true, tables: false, smartypants: true, renderer: utils.customMarkedRenderer({disable: true})});
- comment = (
- <p className='post-link'>
- <span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick} dangerouslySetInnerHTML={{__html: message}} /></span>
- </p>
- );
- } else {
- comment = (
- <p className='post-link'>
- <span>Commented on {name}{apostrophe} message: <a className='theme' onClick={this.props.handleCommentClick}>{message}</a></span>
- </p>
- );
- }
+ comment = (
+ <p className="post-link">
+ <span>Commented on {name}{apostrophe} message: <a className="theme" onClick={this.props.handleCommentClick}>{message}</a></span>
+ </p>
+ );
- postClass += ' post-comment';
+ postClass += " post-comment";
}
var embed;
@@ -74,26 +64,18 @@ module.exports = React.createClass({
embed = utils.getEmbed(this.state.links[0]);
}
- var innerHolder = <p key={post.id + '_message'} className={postClass}><span>{inner}</span></p>;
- if (allowTextFormatting) {
- innerHolder = <div key={post.id + '_message'} className={postClass}><span>{inner}</span></div>;
- }
-
- var fileAttachmentHolder = '';
- if (filenames && filenames.length > 0) {
- fileAttachmentHolder = (<FileAttachmentList
- filenames={filenames}
- modalId={'view_image_modal_' + post.id}
- channelId={post.channel_id}
- userId={post.user_id} />);
- }
-
return (
- <div className='post-body'>
- {comment}
- {innerHolder}
- {fileAttachmentHolder}
- {embed}
+ <div className="post-body">
+ { comment }
+ <p key={post.id+"_message"} className={postClass}><span>{inner}</span></p>
+ { filenames && filenames.length > 0 ?
+ <FileAttachmentList
+ filenames={filenames}
+ modalId={"view_image_modal_" + post.id}
+ channelId={post.channel_id}
+ userId={post.user_id} />
+ : "" }
+ { embed }
</div>
);
}
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index ad7f4a8bf..fa74d4295 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -69,6 +69,12 @@ module.exports = React.createClass({
this.oldScrollHeight = post_holder.scrollHeight;
this.oldZoom = (window.outerWidth - 8) / window.innerWidth;
+ $('.modal').on('show.bs.modal', function () {
+ $('.modal-body').css('overflow-y', 'auto');
+ $('.modal-body').css('max-height', $(window).height() * 0.7);
+ });
+
+ // Timeout exists for the DOM to fully render before making changes
var self = this;
$(window).resize(function(){
$(post_holder).perfectScrollbar('update');
@@ -140,6 +146,7 @@ module.exports = React.createClass({
UserStore.removeStatusesChangeListener(this._onTimeChange);
SocketStore.removeChangeListener(this._onSocketChange);
$('body').off('click.userpopover');
+ $('.modal').off('show.bs.modal')
},
resize: function() {
var post_holder = $(".post-list-holder-by-time")[0];
@@ -197,10 +204,7 @@ module.exports = React.createClass({
var post = post_list.posts[msg.props.post_id];
post.message = msg.props.message;
- post.lastEditDate = Date.now();
-
post_list.posts[post.id] = post;
-
this.setState({ post_list: post_list });
PostStore.storePosts(msg.channel_id, post_list);
@@ -433,13 +437,8 @@ module.exports = React.createClass({
// it is the last comment if it is last post in the channel or the next post has a different root post
var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i-1]].root_id != post.root_id);
- var postKey = post.id;
- if (post.lastEditDate) {
- postKey += post.lastEditDate;
- }
-
var postCtl = (
- <Post ref={post.id} sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={postKey}
+ <Post ref={post.id} sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id}
posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment}
/>
);
diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx
index 19e4cf67a..e46979ff7 100644
--- a/web/react/components/post_right.jsx
+++ b/web/react/components/post_right.jsx
@@ -11,6 +11,7 @@ var SearchBox =require('./search_bar.jsx');
var CreateComment = require( './create_comment.jsx' );
var Constants = require('../utils/constants.jsx');
var FileAttachmentList = require('./file_attachment_list.jsx');
+var FileUploadOverlay = require('./file_upload_overlay.jsx');
var ActionTypes = Constants.ActionTypes;
RhsHeaderPost = React.createClass({
@@ -56,7 +57,6 @@ RhsHeaderPost = React.createClass({
RootPost = React.createClass({
render: function() {
- var allowTextFormatting = config.AllowTextFormatting;
var post = this.props.post;
var message = utils.textToJsx(post.message);
var isOwner = UserStore.getCurrentId() == post.user_id;
@@ -77,11 +77,6 @@ RootPost = React.createClass({
channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
}
- var messageHolder = <p>{message}</p>;
- if (allowTextFormatting) {
- messageHolder = <div>{message}</div>;
- }
-
return (
<div className={"post post--root " + currentUserCss}>
<div className="post-right-channel__name">{ channelName }</div>
@@ -107,7 +102,7 @@ RootPost = React.createClass({
</li>
</ul>
<div className="post-body">
- {messageHolder}
+ <p>{message}</p>
{ post.filenames && post.filenames.length > 0 ?
<FileAttachmentList
filenames={post.filenames}
@@ -125,7 +120,6 @@ RootPost = React.createClass({
CommentPost = React.createClass({
render: function() {
- var allowTextFormatting = config.AllowTextFormatting;
var post = this.props.post;
var commentClass = "post";
@@ -145,11 +139,6 @@ CommentPost = React.createClass({
var message = utils.textToJsx(post.message);
var timestamp = UserStore.getCurrentUser().update_at;
- var messageHolder = <p>{message}</p>;
- if (allowTextFormatting) {
- messageHolder = <div>{message}</div>;
- }
-
return (
<div className={commentClass + " " + currentUserCss}>
<div className="post-profile-img__container">
@@ -172,7 +161,7 @@ CommentPost = React.createClass({
</li>
</ul>
<div className="post-body">
- {messageHolder}
+ <p>{message}</p>
{ post.filenames && post.filenames.length > 0 ?
<FileAttachmentList
filenames={post.filenames}
@@ -285,22 +274,11 @@ module.exports = React.createClass({
root_post = post_list.posts[selected_post.root_id];
}
- var rootPostKey = root_post.id
- if (root_post.lastEditDate) {
- rootPostKey += root_post.lastEditDate;
- }
-
var posts_array = [];
for (var postId in post_list.posts) {
var cpost = post_list.posts[postId];
if (cpost.root_id == root_post.id) {
- var cpostKey = cpost.id
- if (cpost.lastEditDate) {
- cpostKey += cpost.lastEditDate;
- }
-
- cpost.cpostKey = cpostKey;
posts_array.push(cpost);
}
}
@@ -319,14 +297,16 @@ module.exports = React.createClass({
return (
<div className="post-right__container">
+ <FileUploadOverlay
+ overlayType='right' />
<div className="search-bar__container sidebar--right__search-header">{searchForm}</div>
<div className="sidebar-right__body">
<RhsHeaderPost fromSearch={this.props.fromSearch} isMentionSearch={this.props.isMentionSearch} />
<div className="post-right__scroll">
- <RootPost key={rootPostKey} post={root_post} commentCount={posts_array.length}/>
+ <RootPost post={root_post} commentCount={posts_array.length}/>
<div className="post-right-comments-container">
{ posts_array.map(function(cpost) {
- return <CommentPost ref={cpost.id} key={cpost.cpostKey} post={cpost} selected={ (cpost.id == selected_post.id) } />
+ return <CommentPost ref={cpost.id} key={cpost.id} post={cpost} selected={ (cpost.id == selected_post.id) } />
})}
</div>
<div className="post-create__container">
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 8f6bd861a..643ad112b 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -84,8 +84,6 @@ var SearchItem = React.createClass({
channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
}
- var searchItemKey = Date.now().toString();
-
return (
<div className="search-item-container post" onClick={this.handleClick}>
<div className="search-channel__name">{ channelName }</div>
@@ -101,7 +99,7 @@ var SearchItem = React.createClass({
</time>
</li>
</ul>
- <div key={this.props.key + searchItemKey} className="search-item-snippet"><span>{message}</span></div>
+ <div className="search-item-snippet"><span>{message}</span></div>
</div>
</div>
);
@@ -133,7 +131,6 @@ module.exports = React.createClass({
if (this.isMounted()) {
var newState = getStateFromStores();
if (!utils.areStatesEqual(newState, this.state)) {
- newState.last_edit_time = Date.now();
this.setState(newState);
}
}
@@ -155,11 +152,6 @@ module.exports = React.createClass({
var noResults = (!results || !results.order || !results.order.length);
var searchTerm = PostStore.getSearchTerm();
- var searchItemKey = "";
- if (this.state.last_edit_time) {
- searchItemKey += this.state.last_edit_time.toString();
- }
-
return (
<div className="sidebar--right__content">
<div className="search-bar__container sidebar--right__search-header">{searchForm}</div>
@@ -170,7 +162,7 @@ module.exports = React.createClass({
{ noResults ? <div className="sidebar--right__subheader">No results</div>
: results.order.map(function(id) {
var post = results.posts[id];
- return <SearchItem key={searchItemKey + post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
+ return <SearchItem key={post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
}, this)
}
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index 2209c74d1..3c87e416e 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -2,12 +2,23 @@
// See License.txt for license information.
module.exports = React.createClass({
+ displayName: 'SettingsItemMin',
+ propTypes: {
+ title: React.PropTypes.string,
+ disableOpen: React.PropTypes.bool,
+ updateSection: React.PropTypes.func,
+ describe: React.PropTypes.string
+ },
render: function() {
+ var editButton = '';
+ if (!this.props.disableOpen) {
+ editButton = <li className='col-sm-2 section-edit'><a className='section-edit theme' href='#' onClick={this.props.updateSection}>Edit</a></li>;
+ }
return (
- <ul className="section-min">
- <li className="col-sm-10 section-title">{this.props.title}</li>
- <li className="col-sm-2 section-edit"><a className="section-edit theme" href="#" onClick={this.props.updateSection}>Edit</a></li>
- <li className="col-sm-7 section-describe">{this.props.describe}</li>
+ <ul className='section-min'>
+ <li className='col-sm-10 section-title'>{this.props.title}</li>
+ {editButton}
+ <li className='col-sm-7 section-describe'>{this.props.describe}</li>
</ul>
);
}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 988ef4a9c..a8496b385 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -11,7 +11,6 @@ var BrowserStore = require('../stores/browser_store.jsx');
var utils = require('../utils/utils.jsx');
var SidebarHeader = require('./sidebar_header.jsx');
var SearchBox = require('./search_bar.jsx');
-var formatText = require('../../static/js/marked/lib/marked.js');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
@@ -129,6 +128,7 @@ module.exports = React.createClass({
ChannelStore.addChangeListener(this.onChange);
UserStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
+ TeamStore.addChangeListener(this.onChange);
SocketStore.addChangeListener(this.onSocketChange);
$('.nav-pills__container').perfectScrollbar();
@@ -147,6 +147,7 @@ module.exports = React.createClass({
ChannelStore.removeChangeListener(this.onChange);
UserStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
+ TeamStore.removeChangeListener(this.onChange);
SocketStore.removeChangeListener(this.onSocketChange);
},
onChange: function() {
@@ -210,11 +211,6 @@ module.exports = React.createClass({
utils.notifyMe(title, username + ' did something new', channel);
}
} else {
- var allowTextFormatting = config.AllowTextFormatting;
- if (allowTextFormatting) {
- notifyText = formatText(notifyText, {sanitize: false, mangle: false, gfm: true, breaks: true, tables: false, smartypants: true, renderer: utils.customMarkedRenderer({disable: true})});
- }
- notifyText = utils.replaceHtmlEntities(notifyText);
utils.notifyMe(title, username + ' wrote: ' + notifyText, channel);
}
if (!user.notify_props || user.notify_props.desktop_sound === 'true') {
@@ -354,15 +350,16 @@ module.exports = React.createClass({
// set up click handler to switch channels (or create a new channel for non-existant ones)
var clickHandler = null;
- var href;
+ var href = '#';
+ var teamURL = TeamStore.getCurrentTeamUrl();
if (!channel.fake) {
clickHandler = function(e) {
e.preventDefault();
utils.switchChannel(channel);
};
- href = '#';
- } else {
- href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
+ }
+ if (channel.fake && teamURL){
+ href = teamURL + '/channels/' + channel.name;
}
return (
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index cc3f255ee..761c06e74 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -96,7 +96,7 @@ var NavbarDropdown = React.createClass({
<span className='dropdown__icon' dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
</a>
<ul className='dropdown-menu' role='menu'>
- <li><a href='#' data-toggle='modal' data-target='#user_settings1'>Account Settings</a></li>
+ <li><a href='#' data-toggle='modal' data-target='#user_settings'>Account Settings</a></li>
{teamSettings}
{inviteLink}
{teamLink}
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 2439719a1..d221ca840 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -72,7 +72,7 @@ module.exports = React.createClass({
<div className='nav-pills__container'>
<ul className='nav nav-pills nav-stacked'>
- <li><a href='#' data-toggle='modal' data-target='#user_settings1'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
+ <li><a href='#' data-toggle='modal' data-target='#user_settings'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
{teamSettingsLink}
{inviteLink}
{teamLink}
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index 1a0c313d3..8f29bbe57 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -13,6 +13,7 @@ var assign = require('object-assign');
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
+ var soundNeeded = !utils.isBrowserFirefox();
var sound = (!user.notify_props || user.notify_props.desktop_sound == undefined) ? "true" : user.notify_props.desktop_sound;
var desktop = (!user.notify_props || user.notify_props.desktop == undefined) ? "all" : user.notify_props.desktop;
var email = (!user.notify_props || user.notify_props.email == undefined) ? "true" : user.notify_props.email;
@@ -58,7 +59,7 @@ function getNotificationsStateFromStores() {
}
}
- return { notify_level: desktop, enable_email: email, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key };
+ return { notify_level: desktop, enable_email: email, soundNeeded: soundNeeded, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key };
}
@@ -105,11 +106,11 @@ var NotificationsTab = React.createClass({
},
componentDidMount: function() {
UserStore.addChangeListener(this._onChange);
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
UserStore.removeChangeListener(this._onChange);
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
_onChange: function() {
@@ -235,7 +236,7 @@ var NotificationsTab = React.createClass({
}
var soundSection;
- if (this.props.activeSection === 'sound') {
+ if (this.props.activeSection === 'sound' && this.state.soundNeeded) {
var soundActive = ["",""];
if (this.state.enable_sound === "false") {
soundActive[1] = "active";
@@ -265,7 +266,9 @@ var NotificationsTab = React.createClass({
);
} else {
var describe = "";
- if (this.state.enable_sound === "false") {
+ if (!this.state.soundNeeded) {
+ describe = "Please configure notification sounds in your browser settings"
+ } else if (this.state.enable_sound === "false") {
describe = "Off";
} else {
describe = "On";
@@ -276,6 +279,7 @@ var NotificationsTab = React.createClass({
title="Desktop notification sounds"
describe={describe}
updateSection={function(){self.props.updateSection("sound");}}
+ disableOpen = {!this.state.soundNeeded}
/>
);
}
@@ -513,27 +517,34 @@ var SecurityTab = React.createClass({
this.setState({confirmPassword: e.target.value});
},
handleHistoryOpen: function() {
- $('#user_settings1').modal('hide');
+ this.setState({willReturn: true});
+ $("#user_settings").modal('hide');
},
handleDevicesOpen: function() {
- $('#user_settings1').modal('hide');
+ this.setState({willReturn: true});
+ $("#user_settings").modal('hide');
},
handleClose: function() {
$(this.getDOMNode()).find('.form-control').each(function() {
this.value = '';
});
this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
- this.props.updateTab('general');
+
+ if (!this.state.willReturn) {
+ this.props.updateTab('general');
+ } else {
+ this.setState({willReturn: false});
+ }
},
componentDidMount: function() {
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
getInitialState: function() {
- return {currentPassword: '', newPassword: '', confirmPassword: ''};
+ return {currentPassword: '', newPassword: '', confirmPassword: '', willReturn: false};
},
render: function() {
var serverError = this.state.serverError ? this.state.serverError : null;
@@ -811,10 +822,10 @@ var GeneralTab = React.createClass({
this.props.updateSection('');
},
componentDidMount: function() {
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
},
getInitialState: function() {
var user = this.props.user;
@@ -1093,7 +1104,7 @@ var AppearanceTab = React.createClass({
if (this.props.activeSection === "theme") {
$(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
}
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentDidUpdate: function() {
if (this.props.activeSection === "theme") {
@@ -1102,7 +1113,7 @@ var AppearanceTab = React.createClass({
}
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
getInitialState: function() {
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx
index 702e7ad7a..7181c4020 100644
--- a/web/react/components/user_settings_modal.jsx
+++ b/web/react/components/user_settings_modal.jsx
@@ -32,7 +32,7 @@ module.exports = React.createClass({
tabs.push({name: "appearance", ui_name: "Appearance", icon: "glyphicon glyphicon-wrench"});
return (
- <div className="modal fade" ref="modal" id="user_settings1" role="dialog" tabIndex="-1" aria-hidden="true">
+ <div className="modal fade" ref="modal" id="user_settings" role="dialog" tabIndex="-1" aria-hidden="true">
<div className="modal-dialog settings-modal">
<div className="modal-content">
<div className="modal-header">
@@ -64,4 +64,3 @@ module.exports = React.createClass({
);
}
});
-
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 52d537f27..0eeb5fb65 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -34,6 +34,7 @@ var ChannelInfoModal = require('../components/channel_info_modal.jsx');
var AccessHistoryModal = require('../components/access_history_modal.jsx');
var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx')
+var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
var AsyncClient = require('../utils/async_client.jsx');
@@ -225,4 +226,10 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
document.getElementById('removed_from_channel_modal')
);
+ React.render(
+ <FileUploadOverlay
+ overlayType='center' />,
+ document.getElementById('file_upload_overlay')
+ );
+
};
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index e6380d19e..3f2248c44 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -13,89 +13,94 @@ var CHANGE_EVENT = 'change';
var utils;
function getWindowLocationOrigin() {
- if (!utils) utils = require('../utils/utils.jsx');
+ if (!utils) {
+ utils = require('../utils/utils.jsx');
+ }
return utils.getWindowLocationOrigin();
}
var TeamStore = assign({}, EventEmitter.prototype, {
- emitChange: function() {
- this.emit(CHANGE_EVENT);
- },
- addChangeListener: function(callback) {
- this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
- this.removeListener(CHANGE_EVENT, callback);
- },
- get: function(id) {
- var c = this._getTeams();
- return c[id];
- },
- getByName: function(name) {
- var current = null;
- var t = this._getTeams();
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+ addChangeListener: function(callback) {
+ this.on(CHANGE_EVENT, callback);
+ },
+ removeChangeListener: function(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ },
+ get: function(id) {
+ var c = this.pGetTeams();
+ return c[id];
+ },
+ getByName: function(name) {
+ var t = this.pGetTeams();
- for (id in t) {
- if (t[id].name == name) {
- return t[id];
+ for (var id in t) {
+ if (t[id].name === name) {
+ return t[id];
+ }
}
- }
- return null;
- },
- getAll: function() {
- return this._getTeams();
- },
- setCurrentId: function(id) {
- if (id == null)
- BrowserStore.removeItem("current_team_id");
- else
- BrowserStore.setItem("current_team_id", id);
- },
- getCurrentId: function() {
- return BrowserStore.getItem("current_team_id");
- },
- getCurrent: function() {
- var currentId = TeamStore.getCurrentId();
+ return null;
+ },
+ getAll: function() {
+ return this.pGetTeams();
+ },
+ setCurrentId: function(id) {
+ if (id === null) {
+ BrowserStore.removeItem('current_team_id');
+ } else {
+ BrowserStore.setItem('current_team_id', id);
+ }
+ },
+ getCurrentId: function() {
+ return BrowserStore.getItem('current_team_id');
+ },
+ getCurrent: function() {
+ var currentId = TeamStore.getCurrentId();
- if (currentId != null)
- return this.get(currentId);
- else
- return null;
- },
- getCurrentTeamUrl: function() {
- return getWindowLocationOrigin() + "/" + this.getCurrent().name;
- },
- storeTeam: function(team) {
- var teams = this._getTeams();
- teams[team.id] = team;
- this._storeTeams(teams);
- },
- _storeTeams: function(teams) {
- BrowserStore.setItem("user_teams", teams);
- },
- _getTeams: function() {
- return BrowserStore.getItem("user_teams", {});
- }
+ if (currentId !== null) {
+ return this.get(currentId);
+ }
+ return null;
+ },
+ getCurrentTeamUrl: function() {
+ if (this.getCurrent()) {
+ return getWindowLocationOrigin() + '/' + this.getCurrent().name;
+ }
+ return null;
+ },
+ storeTeam: function(team) {
+ var teams = this.pGetTeams();
+ teams[team.id] = team;
+ this.pStoreTeams(teams);
+ },
+ pStoreTeams: function(teams) {
+ BrowserStore.setItem('user_teams', teams);
+ },
+ pGetTeams: function() {
+ return BrowserStore.getItem('user_teams', {});
+ }
});
-TeamStore.dispatchToken = AppDispatcher.register(function(payload) {
- var action = payload.action;
+TeamStore.dispatchToken = AppDispatcher.register(function registry(payload) {
+ var action = payload.action;
- switch(action.type) {
+ switch (action.type) {
- case ActionTypes.CLICK_TEAM:
- TeamStore.setCurrentId(action.id);
- TeamStore.emitChange();
- break;
+ case ActionTypes.CLICK_TEAM:
+ TeamStore.setCurrentId(action.id);
+ TeamStore.emitChange();
+ break;
- case ActionTypes.RECIEVED_TEAM:
- TeamStore.storeTeam(action.team);
- TeamStore.emitChange();
- break;
+ case ActionTypes.RECIEVED_TEAM:
+ TeamStore.storeTeam(action.team);
+ TeamStore.emitChange();
+ break;
- default:
- }
+ default:
+ }
});
module.exports = TeamStore;
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index a903ca063..2312fe225 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -9,7 +9,6 @@ var ActionTypes = Constants.ActionTypes;
var AsyncClient = require('./async_client.jsx');
var client = require('./client.jsx');
var Autolinker = require('autolinker');
-var formatText = require('../../static/js/marked/lib/marked.js');
module.exports.isEmail = function(email) {
var regex = /^([a-zA-Z0-9_.+-])+\@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,4})+$/;
@@ -96,36 +95,39 @@ module.exports.getCookie = function(name) {
if (parts.length == 2) return parts.pop().split(";").shift();
}
+
module.exports.notifyMe = function(title, body, channel) {
- if ('Notification' in window && Notification.permission !== 'denied') {
- Notification.requestPermission(function(permission) {
- if (Notification.permission !== permission) {
- Notification.permission = permission;
- }
+ if ("Notification" in window && Notification.permission !== 'denied') {
+ Notification.requestPermission(function (permission) {
+ if (Notification.permission !== permission) {
+ Notification.permission = permission;
+ }
- if (permission === 'granted') {
- var notification = new Notification(title,
- {body: body, tag: body, icon: '/static/images/icon50x50.gif'}
- );
- notification.onclick = function() {
- window.focus();
- if (channel) {
- module.exports.switchChannel(channel);
- } else {
- window.location.href = '/';
- }
- };
- setTimeout(function() {
- notification.close();
- }, 5000);
- }
- });
- }
-};
+ if (permission === "granted") {
+ var notification = new Notification(title,
+ { body: body, tag: body, icon: '/static/images/icon50x50.gif' }
+ );
+ notification.onclick = function() {
+ window.focus();
+ if (channel) {
+ module.exports.switchChannel(channel);
+ } else {
+ window.location.href = "/";
+ }
+ };
+ setTimeout(function(){
+ notification.close();
+ }, 5000);
+ }
+ });
+ }
+}
module.exports.ding = function() {
- var audio = new Audio('/static/images/ding.mp3');
- audio.play();
+ if (!module.exports.isBrowserFirefox()) {
+ var audio = new Audio('/static/images/ding.mp3');
+ audio.play();
+ }
}
module.exports.getUrlParameter = function(sParam) {
@@ -385,262 +387,112 @@ module.exports.searchForTerm = function(term) {
});
}
-/* Options:
- - disable: Parses out *'s and other format specifiers in the text, but doesn't convert to html
-*/
-module.exports.customMarkedRenderer = function(options) {
- var customTextRenderer = new formatText.Renderer();
- if (options && options.disable) {
- customTextRenderer.paragraph = function(text) {
- return text + ' ';
- };
- customTextRenderer.strong = function(text) {
- return text;
- };
- customTextRenderer.em = function(text) {
- return text;
- };
- customTextRenderer.codespan = function(code) {
- return code;
- };
- customTextRenderer.link = function(href) {
- return href;
- };
- customTextRenderer.image = function(href) {
- return href;
- };
- } else {
- customTextRenderer.link = function(href) {
- return href;
- };
- customTextRenderer.image = function(href) {
- return href;
- };
- }
- return customTextRenderer;
-};
-
-var puncStartRegex = /^((?![@#])[^A-Za-z0-9_<>])+/g;
-var puncEndRegex = /([^A-Za-z0-9_<>])+$/g;
-var startTagRegex = /(<\s*\w.*?>)+/g;
-var endTagRegex = /(<\s*\/\s*\w\s*.*?>|<\s*br\s*>)+/g;
+var oldExplicitMentionRegex = /(?:<mention>)([\s\S]*?)(?:<\/mention>)/g;
+var puncStartRegex = /^((?![@#])\W)+/g;
+var puncEndRegex = /(\W)+$/g;
-module.exports.textToJsx = function(textToChange, options) {
- var useTextFormatting = config.AllowTextFormatting && (!options || !options.noTextFormatting);
- var text = textToChange;
+module.exports.textToJsx = function(text, options) {
- if (useTextFormatting) {
- text = formatText(text, {sanitize: true, mangle: false, gfm: true, breaks: true, tables: false, smartypants: true, renderer: module.exports.customMarkedRenderer()});
+ if (options && options['singleline']) {
+ var repRegex = new RegExp("\n", "g");
+ text = text.replace(repRegex, " ");
}
- if (options && options.singleline) {
- var repRegex = new RegExp('\n', 'g');
- text = text.replace(repRegex, ' ');
+ var searchTerm = ""
+ if (options && options['searchTerm']) {
+ searchTerm = options['searchTerm'].toLowerCase()
}
- var searchTerm = '';
- if (options && options.searchTerm) {
- searchTerm = options.searchTerm.toLowerCase();
- }
-
- var mentionClass = 'mention-highlight';
- if (options && options.noMentionHighlight) {
- mentionClass = '';
+ var mentionClass = "mention-highlight";
+ if (options && options['noMentionHighlight']) {
+ mentionClass = "";
}
var inner = [];
- var codeFlag = false;
- var codeString = '';
// Function specific regex
var hashRegex = /^href="#[^"]+"|(#[A-Za-z]+[A-Za-z0-9_\-]*[A-Za-z0-9])$/g;
var implicitKeywords = UserStore.getCurrentMentionKeys();
- var lines = text.split('\n');
+ var lines = text.split("\n");
for (var i = 0; i < lines.length; i++) {
var line = lines[i];
- var words = line.split(' ');
- var highlightSearchClass = '';
+ var words = line.split(" ");
+ var highlightSearchClass = "";
for (var z = 0; z < words.length; z++) {
var word = words[z];
- var trimWord;
- if (useTextFormatting) {
- trimWord = word.replace(endTagRegex, '').replace(startTagRegex, '').replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
- } else {
- trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
- }
+ var trimWord = word.replace(puncStartRegex, '').replace(puncEndRegex, '').trim();
var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here
var explicitMention = mentionRegex.exec(trimWord);
- var mClass;
-
- var prefix = '';
- var suffix = '';
- var prefixSpan = null;
- var suffixSpan = <span key={word + i + z + 'suf_span'}> </span>;
- if (useTextFormatting) {
- if (word.match(startTagRegex)) {
- prefix += word.match(startTagRegex);
- }
- if (word.replace(startTagRegex, '').match(puncStartRegex)) {
- prefix += word.replace(startTagRegex, '').match(puncStartRegex);
- }
- if (word.match(endTagRegex)) {
- suffix += word.match(endTagRegex);
- }
- if (word.replace(endTagRegex, '').match(puncEndRegex)) {
- suffix += word.replace(endTagRegex, '').match(puncEndRegex);
- }
+ if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm != "") {
- if (prefix) {
- prefixSpan = <span key={word + i + z + 'pre_span'}><span dangerouslySetInnerHTML={{__html: prefix}} /></span>;
- }
- if (suffix) {
- suffixSpan = <span key={word + i + z + 'suf_span'}><span dangerouslySetInnerHTML={{__html: suffix}} /> </span>;
- }
- } else {
- prefix = word.match(puncStartRegex);
- suffix = word.match(puncEndRegex);
- if (prefix) {
- prefixSpan = <span key={word + i + z + 'pre_span'}>{prefix}</span>;
- }
- if (suffix) {
- suffixSpan = <span key={word + i + z + 'suf_span'}>{suffix} </span>;
- }
+ highlightSearchClass = " search-highlight";
}
- if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm !== '') {
- highlightSearchClass = ' search-highlight';
- }
-
- if (useTextFormatting && (codeFlag || word.indexOf('<code>') !== -1)) {
- codeString += word + ' ';
- codeFlag = true;
-
- if (word.indexOf('</code>') !== -1) {
- inner.push(<span key={word + i + z + '_span'} className={highlightSearchClass} dangerouslySetInnerHTML={{__html: codeString}} />);
- codeString = '';
- codeFlag = false;
- }
- } else if (explicitMention &&
- (UserStore.getProfileByUsername(explicitMention[1]) ||
- Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1)) {
+ if (explicitMention &&
+ (UserStore.getProfileByUsername(explicitMention[1]) ||
+ Constants.SPECIAL_MENTIONS.indexOf(explicitMention[1]) !== -1))
+ {
var name = explicitMention[1];
-
// do both a non-case sensitive and case senstive check
- mClass = '';
- if (implicitKeywords.indexOf('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) {
- mClass = mentionClass;
- }
+ var mClass = implicitKeywords.indexOf('@'+name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@'+name) !== -1 ? mentionClass : "";
+
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
if (searchTerm === name) {
- highlightSearchClass = ' search-highlight';
+ highlightSearchClass = " search-highlight";
}
- if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + 'word_span'}><a className={mClass + highlightSearchClass + ' mention-link'} key={name + i + z + '_link'} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>{'@' + name}</a></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
- }
- } else {
- inner.push(<span key={name + i + z + '_span'}>{prefix}<a className={mClass + highlightSearchClass + ' mention-link'} key={name + i + z + '_link'} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
- }
+ inner.push(<span key={name+i+z+"_span"}>{prefix}<a className={mClass + highlightSearchClass + " mention-link"} key={name+i+z+"_link"} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(name)}>@{name}</a>{suffix} </span>);
} else if (testUrlMatch(word).length) {
var match = testUrlMatch(word)[0];
var link = match.link;
- prefix = word.substring(0, word.indexOf(match.text));
- suffix = word.substring(word.indexOf(match.text) + match.text.length);
- if (prefix) {
- prefixSpan = <span key={word + i + z + 'pre_span'}><span dangerouslySetInnerHTML={{__html: prefix}} /></span>;
- }
- if (suffix) {
- suffixSpan = <span key={word + i + z + 'suf_span'}><span dangerouslySetInnerHTML={{__html: suffix}} /> </span>;
- } else {
- suffixSpan = <span key={word + i + z + 'suf_span'}> </span>;
- }
+ var prefix = word.substring(0,word.indexOf(match.text));
+ var suffix = word.substring(word.indexOf(match.text)+match.text.length);
+
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_link"} className={"theme" + highlightSearchClass} target="_blank" href={link}>{match.text}</a>{suffix} </span>);
- if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + '_span'}><a key={name + i + z + '_link'} className={'theme' + highlightSearchClass} target='_blank' href={link}>{match.text}</a></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
- }
- } else {
- inner.push(<span key={word + i + z + '_span'}>{prefix}<a key={word + i + z + '_link'} className={'theme' + highlightSearchClass} target='_blank' href={link}>{match.text}</a>{suffix} </span>);
- }
} else if (trimWord.match(hashRegex)) {
- mClass = '';
- if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
- mClass = mentionClass;
- }
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
+ var mClass = implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1 ? mentionClass : "";
if (searchTerm === trimWord.substring(1).toLowerCase() || searchTerm === trimWord.toLowerCase()) {
- highlightSearchClass = ' search-highlight';
+ highlightSearchClass = " search-highlight";
}
- if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + '_span'}><a key={word + i + z + '_hash'} className={'theme ' + mClass + highlightSearchClass} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
- }
- } else {
- inner.push(<span key={word + i + z + '_span'}>{prefix}<a key={word + i + z + '_hash'} className={'theme ' + mClass + highlightSearchClass} href='#' onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
- }
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<a key={word+i+z+"_hash"} className={"theme " + mClass + highlightSearchClass} href="#" onClick={function(value) { return function() { module.exports.searchForTerm(value); } }(trimWord)}>{trimWord}</a>{suffix} </span>);
+
} else if (implicitKeywords.indexOf(trimWord) !== -1 || implicitKeywords.indexOf(trimWord.toLowerCase()) !== -1) {
+ var suffix = word.match(puncEndRegex);
+ var prefix = word.match(puncStartRegex);
+
if (trimWord.charAt(0) === '@') {
if (searchTerm === trimWord.substring(1).toLowerCase()) {
- highlightSearchClass = ' search-highlight';
- }
- if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + '_span'}><a className={mentionClass + highlightSearchClass} key={name + i + z + '_link'} href='#'>{trimWord}</a></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
- }
- } else {
- inner.push(<span key={word + i + z + '_span'}>{prefix}<a className={mentionClass + highlightSearchClass} key={name + i + z + '_link'} href='#'>{trimWord}</a>{suffix} </span>);
- }
- } else if (useTextFormatting) {
- if (prefixSpan) {
- inner.push(prefixSpan);
- }
- inner.push(<span key={word + i + z + '_span'}><span className={mentionClass + highlightSearchClass}>{trimWord}</span></span>);
- if (suffixSpan) {
- inner.push(suffixSpan);
+ highlightSearchClass = " search-highlight";
}
+ inner.push(<span key={word+i+z+"_span"} key={name+i+z+"_span"}>{prefix}<a className={mentionClass + highlightSearchClass} key={name+i+z+"_link"} href="#">{trimWord}</a>{suffix} </span>);
} else {
- inner.push(<span key={word + i + z + '_span'}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
- }
- } else if (word !== '') {
- if (useTextFormatting) {
- inner.push(<span key={word + i + z + '_span'} className={highlightSearchClass} dangerouslySetInnerHTML={{__html: word + ' '}} />);
- } else {
- inner.push(<span key={word + i + z + '_span'}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
+ inner.push(<span key={word+i+z+"_span"}>{prefix}<span className={mentionClass + highlightSearchClass}>{module.exports.replaceHtmlEntities(trimWord)}</span>{suffix} </span>);
}
+
+ } else if (word === "") {
+ // if word is empty dont include a span
+ } else {
+ inner.push(<span key={word+i+z+"_span"}><span className={highlightSearchClass}>{module.exports.replaceHtmlEntities(word)}</span> </span>);
}
- highlightSearchClass = '';
- }
- if (!useTextFormatting && i !== lines.length - 1) {
- inner.push(<br key={'br_' + i}/>);
- } else if (useTextFormatting && !codeFlag && i < lines.length - 2) {
- inner.push(<br key={'br_' + i}/>);
+ highlightSearchClass = "";
}
+ if (i != lines.length-1)
+ inner.push(<br key={"br_"+i+z}/>);
}
return inner;
-};
+}
module.exports.getFileType = function(ext) {
ext = ext.toLowerCase();
@@ -1095,3 +947,7 @@ module.exports.generateId = function() {
return id;
};
+
+module.exports.isBrowserFirefox = function() {
+ return navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+}
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index 78006ff18..5b68b488f 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -24,6 +24,7 @@ body {
height: 100%;
> .row.main {
height: 100%;
+ position: relative;
}
}
> .container-fluid {
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index 65775f01e..1375a10e7 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -115,7 +115,6 @@
height: 100px;
float: left;
margin: 5px 10px 5px 0;
- display: table;
border: 1px solid lightgrey;
.post__load {
height: 100%;
@@ -137,16 +136,16 @@
}
}
.post-image__thumbnail {
- display: table-cell;
- vertical-align: top;
+ float: left;
width: 50%;
height: 100%;
cursor: zoom-in;
cursor: -webkit-zoom-in;
}
.post-image__details {
- display: table-cell;
- vertical-align: top;
+ float: left;
+ @include clearfix;
+ word-break: break-word;
width: 50%;
height: 100%;
background: white;
@@ -194,11 +193,12 @@
border-right: 1px solid #ddd;
vertical-align: center;
- // helper to center the image icon in the preview window
- .file-details__preview-helper {
- height: 100%;
- display: inline-block;
- vertical-align: middle;
- }
- }
+ // helper to center the image icon in the preview window
+ .file-details__preview-helper {
+ height: 100%;
+ display: inline-block;
+ vertical-align: middle;
}
+ }
+}
+
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 441c7e86f..56b31205b 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -106,6 +106,32 @@ body.ios {
}
}
+.file-overlay {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.6);
+ text-align: center;
+ color: #FFF;
+ display: table;
+ font-size: 1.7em;
+ font-weight: 600;
+ z-index: 6;
+
+ > div {
+ display: table-cell;
+ vertical-align: middle;
+ }
+
+ .fa {
+ display: block;
+ font-size: 2em;
+ margin: 0 0 0.3em;
+ }
+}
+
#post-list {
.post-list-holder-by-time {
background: #fff;
@@ -194,19 +220,11 @@ body.ios {
}
}
.msg-typing {
- padding-bottom: 5px;
min-height: 20px;
line-height: 18px;
display: inline-block;
font-size: 13px;
color: #777;
- .code-info {
- padding-left: 2px;
- padding-right: 2px;
- }
- .preformatted-info {
- background-color: #E3E8E3;
- }
}
}
}
diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss
index f3fe9362b..4cf3e32a1 100644
--- a/web/sass-files/sass/partials/_post_right.scss
+++ b/web/sass-files/sass/partials/_post_right.scss
@@ -33,19 +33,6 @@
float: left;
padding-top: 17px;
}
- .msg-format-help {
- min-height: 20px;
- line-height: 18px;
- display: inline-block;
- font-size: 13px;
- color: #555;
- float: left;
- padding-top: 17px;
- .code-info {
- padding-left: 1px;
- padding-right: 1px;
- }
- }
.post-create-footer {
padding-top: 10px;
}
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 47b2b6bd7..733d81c2b 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -189,6 +189,9 @@
}
@media screen and (max-width: 960px) {
+ .center-file-overlay {
+ font-size: 1.5em;
+ }
.post {
.post-header .post-header-col.post-header__reply {
.comment-icon__container__hide {
@@ -240,6 +243,9 @@
}
}
@media screen and (max-width: 768px) {
+ .center-file-overlay {
+ font-size: 1.3em;
+ }
.date-separator, .new-separator {
&.hovered--after {
&:before {
@@ -431,9 +437,6 @@
}
}
}
- #user_settings {
- border-right: none;
- }
body {
&.white {
.inner__wrap {
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 1fb078bb9..0262ef60c 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -143,12 +143,6 @@
}
}
-#user_settings {
- padding: 0 0.5em;
- border-right: 1px solid #ddd;
- max-width: 800px;
-}
-
.channel-settings {
padding: 0 10px;
}
diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss
index 5d866715e..2376c9212 100644
--- a/web/sass-files/sass/partials/_sidebar--left.scss
+++ b/web/sass-files/sass/partials/_sidebar--left.scss
@@ -6,6 +6,7 @@
border-right: $border-gray;
padding: 0 0 2em 0;
background: #fafafa;
+ z-index: 5;
&.sidebar--padded {
padding-top: 44px;
}
diff --git a/web/static/config/config.js b/web/static/config/config.js
index ca4649299..00cae7ab2 100644
--- a/web/static/config/config.js
+++ b/web/static/config/config.js
@@ -15,7 +15,6 @@ var config = {
AllowInviteNames: true,
RequireInviteNames: false,
AllowSignupDomainsWizard: false,
- AllowTextFormatting: true,
// Google Developer Key (for Youtube API links)
// Leave blank to disable
diff --git a/web/static/js/marked/LICENSE b/web/static/js/jquery-dragster/LICENSE
index a7b812ed6..b8b51dc0b 100644
--- a/web/static/js/marked/LICENSE
+++ b/web/static/js/jquery-dragster/LICENSE
@@ -1,4 +1,6 @@
-Copyright (c) 2011-2014, Christopher Jeffrey (https://github.com/chjj/)
+The MIT License (MIT)
+
+Copyright (c) 2015 Jan Martin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -7,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/web/static/js/jquery-dragster/README.md b/web/static/js/jquery-dragster/README.md
new file mode 100644
index 000000000..1c28adaf0
--- /dev/null
+++ b/web/static/js/jquery-dragster/README.md
@@ -0,0 +1,17 @@
+Include [jquery.dragster.js](https://rawgithub.com/catmanjan/jquery-dragster/master/jquery.dragster.js) in page.
+
+Works in IE.
+
+```javascript
+$('.element').dragster({
+ enter: function (dragsterEvent, event) {
+ $(this).addClass('hover');
+ },
+ leave: function (dragsterEvent, event) {
+ $(this).removeClass('hover');
+ },
+ drop: function (dragsterEvent, event) {
+ $(this).removeClass('hover');
+ }
+});
+``` \ No newline at end of file
diff --git a/web/static/js/jquery-dragster/jquery.dragster.js b/web/static/js/jquery-dragster/jquery.dragster.js
new file mode 100644
index 000000000..db73fe3f0
--- /dev/null
+++ b/web/static/js/jquery-dragster/jquery.dragster.js
@@ -0,0 +1,85 @@
+// 1.0.3
+/*
+The MIT License (MIT)
+
+Copyright (c) 2015 Jan Martin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+(function ($) {
+
+ $.fn.dragster = function (options) {
+ var settings = $.extend({
+ enter: $.noop,
+ leave: $.noop,
+ over: $.noop,
+ drop: $.noop
+ }, options);
+
+ return this.each(function () {
+ var first = false,
+ second = false,
+ $this = $(this);
+
+ $this.on({
+ dragenter: function (event) {
+ if (first) {
+ second = true;
+ return;
+ } else {
+ first = true;
+ $this.trigger('dragster:enter', event);
+ }
+ event.preventDefault();
+ },
+ dragleave: function (event) {
+ if (second) {
+ second = false;
+ } else if (first) {
+ first = false;
+ }
+ if (!first && !second) {
+ $this.trigger('dragster:leave', event);
+ }
+ event.preventDefault();
+ },
+ dragover: function (event) {
+ $this.trigger('dragster:over', event);
+ event.preventDefault();
+ },
+ drop: function (event) {
+ if (second) {
+ second = false;
+ } else if (first) {
+ first = false;
+ }
+ if (!first && !second) {
+ $this.trigger('dragster:drop', event);
+ }
+ event.preventDefault();
+ },
+ 'dragster:enter': settings.enter,
+ 'dragster:leave': settings.leave,
+ 'dragster:over': settings.over,
+ 'dragster:drop': settings.drop
+ });
+ });
+ };
+
+}(jQuery));
diff --git a/web/static/js/marked/Makefile b/web/static/js/marked/Makefile
deleted file mode 100644
index d9349f079..000000000
--- a/web/static/js/marked/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-all:
- @cp lib/marked.js marked.js
- @uglifyjs --comments '/\*[^\0]+?Copyright[^\0]+?\*/' -o marked.min.js lib/marked.js
-
-clean:
- @rm marked.js
- @rm marked.min.js
-
-bench:
- @node test --bench
-
-.PHONY: clean all
diff --git a/web/static/js/marked/README.md b/web/static/js/marked/README.md
deleted file mode 100644
index efa71aaaa..000000000
--- a/web/static/js/marked/README.md
+++ /dev/null
@@ -1,406 +0,0 @@
-# marked
-
-> A full-featured markdown parser and compiler, written in JavaScript. Built
-> for speed.
-
-[![NPM version](https://badge.fury.io/js/marked.png)][badge]
-
-## Install
-
-``` bash
-npm install marked --save
-```
-
-## Usage
-
-Minimal usage:
-
-```js
-var marked = require('marked');
-console.log(marked('I am using __markdown__.'));
-// Outputs: <p>I am using <strong>markdown</strong>.</p>
-```
-
-Example setting options with default values:
-
-```js
-var marked = require('marked');
-marked.setOptions({
- renderer: new marked.Renderer(),
- gfm: true,
- tables: true,
- breaks: false,
- pedantic: false,
- sanitize: true,
- smartLists: true,
- smartypants: false
-});
-
-console.log(marked('I am using __markdown__.'));
-```
-
-### Browser
-
-```html
-<!doctype html>
-<html>
-<head>
- <meta charset="utf-8"/>
- <title>Marked in the browser</title>
- <script src="lib/marked.js"></script>
-</head>
-<body>
- <div id="content"></div>
- <script>
- document.getElementById('content').innerHTML =
- marked('# Marked in browser\n\nRendered by **marked**.');
- </script>
-</body>
-</html>
-```
-
-## marked(markdownString [,options] [,callback])
-
-### markdownString
-
-Type: `string`
-
-String of markdown source to be compiled.
-
-### options
-
-Type: `object`
-
-Hash of options. Can also be set using the `marked.setOptions` method as seen
-above.
-
-### callback
-
-Type: `function`
-
-Function called when the `markdownString` has been fully parsed when using
-async highlighting. If the `options` argument is omitted, this can be used as
-the second argument.
-
-## Options
-
-### highlight
-
-Type: `function`
-
-A function to highlight code blocks. The first example below uses async highlighting with
-[node-pygmentize-bundled][pygmentize], and the second is a synchronous example using
-[highlight.js][highlight]:
-
-```js
-var marked = require('marked');
-
-var markdownString = '```js\n console.log("hello"); \n```';
-
-// Async highlighting with pygmentize-bundled
-marked.setOptions({
- highlight: function (code, lang, callback) {
- require('pygmentize-bundled')({ lang: lang, format: 'html' }, code, function (err, result) {
- callback(err, result.toString());
- });
- }
-});
-
-// Using async version of marked
-marked(markdownString, function (err, content) {
- if (err) throw err;
- console.log(content);
-});
-
-// Synchronous highlighting with highlight.js
-marked.setOptions({
- highlight: function (code) {
- return require('highlight.js').highlightAuto(code).value;
- }
-});
-
-console.log(marked(markdownString));
-```
-
-#### highlight arguments
-
-`code`
-
-Type: `string`
-
-The section of code to pass to the highlighter.
-
-`lang`
-
-Type: `string`
-
-The programming language specified in the code block.
-
-`callback`
-
-Type: `function`
-
-The callback function to call when using an async highlighter.
-
-### renderer
-
-Type: `object`
-Default: `new Renderer()`
-
-An object containing functions to render tokens to HTML.
-
-#### Overriding renderer methods
-
-The renderer option allows you to render tokens in a custom manner. Here is an
-example of overriding the default heading token rendering by adding an embedded anchor tag like on GitHub:
-
-```javascript
-var marked = require('marked');
-var renderer = new marked.Renderer();
-
-renderer.heading = function (text, level) {
- var escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
-
- return '<h' + level + '><a name="' +
- escapedText +
- '" class="anchor" href="#' +
- escapedText +
- '"><span class="header-link"></span></a>' +
- text + '</h' + level + '>';
-},
-
-console.log(marked('# heading+', { renderer: renderer }));
-```
-This code will output the following HTML:
-```html
-<h1>
- <a name="heading-" class="anchor" href="#heading-">
- <span class="header-link"></span>
- </a>
- heading+
-</h1>
-```
-
-#### Block level renderer methods
-
-- code(*string* code, *string* language)
-- blockquote(*string* quote)
-- html(*string* html)
-- heading(*string* text, *number* level)
-- hr()
-- list(*string* body, *boolean* ordered)
-- listitem(*string* text)
-- paragraph(*string* text)
-- table(*string* header, *string* body)
-- tablerow(*string* content)
-- tablecell(*string* content, *object* flags)
-
-`flags` has the following properties:
-
-```js
-{
- header: true || false,
- align: 'center' || 'left' || 'right'
-}
-```
-
-#### Inline level renderer methods
-
-- strong(*string* text)
-- em(*string* text)
-- codespan(*string* code)
-- br()
-- del(*string* text)
-- link(*string* href, *string* title, *string* text)
-- image(*string* href, *string* title, *string* text)
-
-### gfm
-
-Type: `boolean`
-Default: `true`
-
-Enable [GitHub flavored markdown][gfm].
-
-### tables
-
-Type: `boolean`
-Default: `true`
-
-Enable GFM [tables][tables].
-This option requires the `gfm` option to be true.
-
-### breaks
-
-Type: `boolean`
-Default: `false`
-
-Enable GFM [line breaks][breaks].
-This option requires the `gfm` option to be true.
-
-### pedantic
-
-Type: `boolean`
-Default: `false`
-
-Conform to obscure parts of `markdown.pl` as much as possible. Don't fix any of
-the original markdown bugs or poor behavior.
-
-### sanitize
-
-Type: `boolean`
-Default: `false`
-
-Sanitize the output. Ignore any HTML that has been input.
-
-### smartLists
-
-Type: `boolean`
-Default: `true`
-
-Use smarter list behavior than the original markdown. May eventually be
-default with the old behavior moved into `pedantic`.
-
-### smartypants
-
-Type: `boolean`
-Default: `false`
-
-Use "smart" typograhic punctuation for things like quotes and dashes.
-
-## Access to lexer and parser
-
-You also have direct access to the lexer and parser if you so desire.
-
-``` js
-var tokens = marked.lexer(text, options);
-console.log(marked.parser(tokens));
-```
-
-``` js
-var lexer = new marked.Lexer(options);
-var tokens = lexer.lex(text);
-console.log(tokens);
-console.log(lexer.rules);
-```
-
-## CLI
-
-``` bash
-$ marked -o hello.html
-hello world
-^D
-$ cat hello.html
-<p>hello world</p>
-```
-
-## Philosophy behind marked
-
-The point of marked was to create a markdown compiler where it was possible to
-frequently parse huge chunks of markdown without having to worry about
-caching the compiled output somehow...or blocking for an unnecesarily long time.
-
-marked is very concise and still implements all markdown features. It is also
-now fully compatible with the client-side.
-
-marked more or less passes the official markdown test suite in its
-entirety. This is important because a surprising number of markdown compilers
-cannot pass more than a few tests. It was very difficult to get marked as
-compliant as it is. It could have cut corners in several areas for the sake
-of performance, but did not in order to be exactly what you expect in terms
-of a markdown rendering. In fact, this is why marked could be considered at a
-disadvantage in the benchmarks above.
-
-Along with implementing every markdown feature, marked also implements [GFM
-features][gfmf].
-
-## Benchmarks
-
-node v0.8.x
-
-``` bash
-$ node test --bench
-marked completed in 3411ms.
-marked (gfm) completed in 3727ms.
-marked (pedantic) completed in 3201ms.
-robotskirt completed in 808ms.
-showdown (reuse converter) completed in 11954ms.
-showdown (new converter) completed in 17774ms.
-markdown-js completed in 17191ms.
-```
-
-__Marked is now faster than Discount, which is written in C.__
-
-For those feeling skeptical: These benchmarks run the entire markdown test suite 1000 times. The test suite tests every feature. It doesn't cater to specific aspects.
-
-### Pro level
-
-You also have direct access to the lexer and parser if you so desire.
-
-``` js
-var tokens = marked.lexer(text, options);
-console.log(marked.parser(tokens));
-```
-
-``` js
-var lexer = new marked.Lexer(options);
-var tokens = lexer.lex(text);
-console.log(tokens);
-console.log(lexer.rules);
-```
-
-``` bash
-$ node
-> require('marked').lexer('> i am using marked.')
-[ { type: 'blockquote_start' },
- { type: 'paragraph',
- text: 'i am using marked.' },
- { type: 'blockquote_end' },
- links: {} ]
-```
-
-## Running Tests & Contributing
-
-If you want to submit a pull request, make sure your changes pass the test
-suite. If you're adding a new feature, be sure to add your own test.
-
-The marked test suite is set up slightly strangely: `test/new` is for all tests
-that are not part of the original markdown.pl test suite (this is where your
-test should go if you make one). `test/original` is only for the original
-markdown.pl tests. `test/tests` houses both types of tests after they have been
-combined and moved/generated by running `node test --fix` or `marked --test
---fix`.
-
-In other words, if you have a test to add, add it to `test/new/` and then
-regenerate the tests with `node test --fix`. Commit the result. If your test
-uses a certain feature, for example, maybe it assumes GFM is *not* enabled, you
-can add `.nogfm` to the filename. So, `my-test.text` becomes
-`my-test.nogfm.text`. You can do this with any marked option. Say you want
-line breaks and smartypants enabled, your filename should be:
-`my-test.breaks.smartypants.text`.
-
-To run the tests:
-
-``` bash
-cd marked/
-node test
-```
-
-### Contribution and License Agreement
-
-If you contribute code to this project, you are implicitly allowing your code
-to be distributed under the MIT license. You are also implicitly verifying that
-all code is your original work. `</legalese>`
-
-## License
-
-Copyright (c) 2011-2014, Christopher Jeffrey. (MIT License)
-
-See LICENSE for more info.
-
-[gfm]: https://help.github.com/articles/github-flavored-markdown
-[gfmf]: http://github.github.com/github-flavored-markdown/
-[pygmentize]: https://github.com/rvagg/node-pygmentize-bundled
-[highlight]: https://github.com/isagalaev/highlight.js
-[badge]: http://badge.fury.io/js/marked
-[tables]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-tables
-[breaks]: https://help.github.com/articles/github-flavored-markdown#newlines
diff --git a/web/static/js/marked/bin/marked b/web/static/js/marked/bin/marked
deleted file mode 100755
index 64254fc3e..000000000
--- a/web/static/js/marked/bin/marked
+++ /dev/null
@@ -1,187 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Marked CLI
- * Copyright (c) 2011-2013, Christopher Jeffrey (MIT License)
- */
-
-var fs = require('fs')
- , util = require('util')
- , marked = require('../');
-
-/**
- * Man Page
- */
-
-function help() {
- var spawn = require('child_process').spawn;
-
- var options = {
- cwd: process.cwd(),
- env: process.env,
- setsid: false,
- customFds: [0, 1, 2]
- };
-
- spawn('man',
- [__dirname + '/../man/marked.1'],
- options);
-}
-
-/**
- * Main
- */
-
-function main(argv, callback) {
- var files = []
- , options = {}
- , input
- , output
- , arg
- , tokens
- , opt;
-
- function getarg() {
- var arg = argv.shift();
-
- if (arg.indexOf('--') === 0) {
- // e.g. --opt
- arg = arg.split('=');
- if (arg.length > 1) {
- // e.g. --opt=val
- argv.unshift(arg.slice(1).join('='));
- }
- arg = arg[0];
- } else if (arg[0] === '-') {
- if (arg.length > 2) {
- // e.g. -abc
- argv = arg.substring(1).split('').map(function(ch) {
- return '-' + ch;
- }).concat(argv);
- arg = argv.shift();
- } else {
- // e.g. -a
- }
- } else {
- // e.g. foo
- }
-
- return arg;
- }
-
- while (argv.length) {
- arg = getarg();
- switch (arg) {
- case '--test':
- return require('../test').main(process.argv.slice());
- case '-o':
- case '--output':
- output = argv.shift();
- break;
- case '-i':
- case '--input':
- input = argv.shift();
- break;
- case '-t':
- case '--tokens':
- tokens = true;
- break;
- case '-h':
- case '--help':
- return help();
- default:
- if (arg.indexOf('--') === 0) {
- opt = camelize(arg.replace(/^--(no-)?/, ''));
- if (!marked.defaults.hasOwnProperty(opt)) {
- continue;
- }
- if (arg.indexOf('--no-') === 0) {
- options[opt] = typeof marked.defaults[opt] !== 'boolean'
- ? null
- : false;
- } else {
- options[opt] = typeof marked.defaults[opt] !== 'boolean'
- ? argv.shift()
- : true;
- }
- } else {
- files.push(arg);
- }
- break;
- }
- }
-
- function getData(callback) {
- if (!input) {
- if (files.length <= 2) {
- return getStdin(callback);
- }
- input = files.pop();
- }
- return fs.readFile(input, 'utf8', callback);
- }
-
- return getData(function(err, data) {
- if (err) return callback(err);
-
- data = tokens
- ? JSON.stringify(marked.lexer(data, options), null, 2)
- : marked(data, options);
-
- if (!output) {
- process.stdout.write(data + '\n');
- return callback();
- }
-
- return fs.writeFile(output, data, callback);
- });
-}
-
-/**
- * Helpers
- */
-
-function getStdin(callback) {
- var stdin = process.stdin
- , buff = '';
-
- stdin.setEncoding('utf8');
-
- stdin.on('data', function(data) {
- buff += data;
- });
-
- stdin.on('error', function(err) {
- return callback(err);
- });
-
- stdin.on('end', function() {
- return callback(null, buff);
- });
-
- try {
- stdin.resume();
- } catch (e) {
- callback(e);
- }
-}
-
-function camelize(text) {
- return text.replace(/(\w)-(\w)/g, function(_, a, b) {
- return a + b.toUpperCase();
- });
-}
-
-/**
- * Expose / Entry Point
- */
-
-if (!module.parent) {
- process.title = 'marked';
- main(process.argv.slice(), function(err, code) {
- if (err) throw err;
- return process.exit(code || 0);
- });
-} else {
- module.exports = main;
-}
diff --git a/web/static/js/marked/lib/marked.js b/web/static/js/marked/lib/marked.js
deleted file mode 100644
index 7f2438069..000000000
--- a/web/static/js/marked/lib/marked.js
+++ /dev/null
@@ -1,980 +0,0 @@
-/**
- * marked - a markdown parser
- * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
- * https://github.com/chjj/marked
- */
-
-;(function() {
-
-/**
- * Block-Level Grammar
- */
-
-var block = {
- newline: /^\n+/,
- code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,
- fences: noop,
- hr: /^( *[-*_]){3,} *(?:\n+|$)/,
- heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,
- nptable: noop,
- lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,
- blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,
- list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,
- html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,
- def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,
- table: noop,
- paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,
- text: /^[^\n]+/
-};
-
-block.bullet = /(?:[*+-]|\d+\.)/;
-block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;
-block.item = replace(block.item, 'gm')
- (/bull/g, block.bullet)
- ();
-
-block.list = replace(block.list)
- (/bull/g, block.bullet)
- ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))')
- ('def', '\\n+(?=' + block.def.source + ')')
- ();
-
-block.blockquote = replace(block.blockquote)
- ('def', block.def)
- ();
-
-block._tag = '(?!(?:'
- + 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code'
- + '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo'
- + '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b';
-
-block.html = replace(block.html)
- ('comment', /<!--[\s\S]*?-->/)
- ('closed', /<(tag)[\s\S]+?<\/\1>/)
- ('closing', /<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)
- (/tag/g, block._tag)
- ();
-
-block.paragraph = replace(block.paragraph)
- ('hr', block.hr)
- ('heading', block.heading)
- ('lheading', block.lheading)
- ('blockquote', block.blockquote)
- ('tag', '<' + block._tag)
- ('def', block.def)
- ();
-
-/**
- * Normal Block Grammar
- */
-
-block.normal = merge({}, block);
-
-/**
- * GFM Block Grammar
- */
-
-block.gfm = merge({}, block.normal, {
- fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,
- paragraph: /^/,
- heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
-});
-
-block.gfm.paragraph = replace(block.paragraph)
- ('(?!', '(?!'
- + block.gfm.fences.source.replace('\\1', '\\2') + '|'
- + block.list.source.replace('\\1', '\\3') + '|')
- ();
-
-/**
- * GFM + Tables Block Grammar
- */
-
-block.tables = merge({}, block.gfm, {
- nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,
- table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/
-});
-
-/**
- * Block Lexer
- */
-
-function Lexer(options) {
- this.tokens = [];
- this.tokens.links = {};
- this.options = options || marked.defaults;
- this.rules = block.normal;
-
- if (this.options.gfm) {
- if (this.options.tables) {
- this.rules = block.tables;
- } else {
- this.rules = block.gfm;
- }
- }
-}
-
-/**
- * Expose Block Rules
- */
-
-Lexer.rules = block;
-
-/**
- * Static Lex Method
- */
-
-Lexer.lex = function(src, options) {
- var lexer = new Lexer(options);
- return lexer.lex(src);
-};
-
-/**
- * Preprocessing
- */
-
-Lexer.prototype.lex = function(src) {
- src = src
- .replace(/\r\n|\r/g, '\n')
- .replace(/\t/g, ' ')
- .replace(/\u00a0/g, ' ')
- .replace(/\u2424/g, '\n');
-
- return this.token(src, true);
-};
-
-/**
- * Lexing
- */
-
-Lexer.prototype.token = function(src, top, bq) {
- var src = src.replace(/^ +$/gm, '')
- , next
- , loose
- , cap
- , bull
- , b
- , item
- , space
- , i
- , l;
-
- while (src) {
- // newline
- if (cap = this.rules.newline.exec(src)) {
- src = src.substring(cap[0].length);
- if (cap[0].length > 1) {
- this.tokens.push({
- type: 'space'
- });
- }
- }
-
- // def
- if ((!bq && top) && (cap = this.rules.def.exec(src))) {
- src = src.substring(cap[0].length);
- this.tokens.links[cap[1].toLowerCase()] = {
- href: cap[2],
- title: cap[3]
- };
- continue;
- }
-
- // html
- if (cap = this.rules.html.exec(src)) {
- src = src.substring(cap[0].length);
- this.tokens.push({
- type: this.options.sanitize
- ? 'paragraph'
- : 'html',
- pre: !this.options.sanitizer
- && (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'),
- text: cap[0]
- });
- continue;
- }
-
- // text
- if (cap = this.rules.text.exec(src)) {
- // Top-level should never reach here.
- src = src.substring(cap[0].length);
- this.tokens.push({
- type: 'text',
- text: cap[0]
- });
- continue;
- }
-
- if (src) {
- throw new
- Error('Infinite loop on byte: ' + src.charCodeAt(0));
- }
- }
-
- return this.tokens;
-};
-
-/**
- * Inline-Level Grammar
- */
-
-var inline = {
- escape: /^\\([\\`*{}\[\]()#+\-.!_>])/,
- autolink: /^<([^ >]+(@|:\/)[^ >]+)>/,
- url: noop,
- tag: /^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,
- link: /^!?\[(inside)\]\(href\)/,
- reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/,
- nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,
- strong: /^(\*{1})\s*([^\r?\n|\r]*?[^\*])\s*\1(?!\*)/,
- em: /^(_{1})\s*([^\r?\n|\r]*?[^_])\s*\1(?!_)/,
- code: /^(`{1})\s*([^\r?\n|\r]*?[^`])\s*\1(?!`)/,
- br: /^ {2,}\n(?!\s*$)/,
- del: noop,
- text: /^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/
-};
-
-inline._inside = /(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;
-inline._href = /\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;
-
-inline.link = replace(inline.link)
- ('inside', inline._inside)
- ('href', inline._href)
- ();
-
-inline.reflink = replace(inline.reflink)
- ('inside', inline._inside)
- ();
-
-/**
- * Normal Inline Grammar
- */
-
-inline.normal = merge({}, inline);
-
-/**
- * Pedantic Inline Grammar
- */
-
-inline.pedantic = merge({}, inline.normal, {
- strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,
- em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/
-});
-
-/**
- * GFM Inline Grammar
- */
-
-inline.gfm = merge({}, inline.normal, {
- escape: replace(inline.escape)('])', '~|])')(),
- url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,
- del: /^~(?=\S)([\s\S]*?\S)~/,
- text: replace(inline.text)
- (']|', '~]|')
- ('|', '|https?://|')
- ()
-});
-
-/**
- * GFM + Line Breaks Inline Grammar
- */
-
-inline.breaks = merge({}, inline.gfm, {
- br: replace(inline.br)('{2,}', '*')(),
- text: replace(inline.gfm.text)('{2,}', '*')()
-});
-
-/**
- * Inline Lexer & Compiler
- */
-
-function InlineLexer(links, options) {
- this.options = options || marked.defaults;
- this.links = links;
- this.rules = inline.normal;
- this.renderer = this.options.renderer || new Renderer;
- this.renderer.options = this.options;
-
- if (!this.links) {
- throw new
- Error('Tokens array requires a `links` property.');
- }
-
- if (this.options.gfm) {
- if (this.options.breaks) {
- this.rules = inline.breaks;
- } else {
- this.rules = inline.gfm;
- }
- } else if (this.options.pedantic) {
- this.rules = inline.pedantic;
- }
-}
-
-/**
- * Expose Inline Rules
- */
-
-InlineLexer.rules = inline;
-
-/**
- * Static Lexing/Compiling Method
- */
-
-InlineLexer.output = function(src, links, options) {
- var inline = new InlineLexer(links, options);
- return inline.output(src);
-};
-
-/**
- * Lexing/Compiling
- */
-
-InlineLexer.prototype.output = function(src) {
- var out = ''
- , link
- , text
- , href
- , cap;
-
- while (src) {
- // escape
- if (cap = this.rules.escape.exec(src)) {
- src = src.substring(cap[0].length);
- out += cap[1];
- continue;
- }
-
- // autolink
- if (cap = this.rules.autolink.exec(src)) {
- src = src.substring(cap[0].length);
- if (cap[2] === '@') {
- text = cap[1].charAt(6) === ':'
- ? this.mangle(cap[1].substring(7))
- : this.mangle(cap[1]);
- href = text;
- } else {
- text = escape(cap[1]);
- href = text;
- }
- out += this.renderer.link(href, null, text);
- continue;
- }
-
- // strong
- if (cap = this.rules.strong.exec(src)) {
- src = src.substring(cap[0].length);
- out += this.renderer.strong(this.output(cap[2] || cap[1]));
- continue;
- }
-
- // em
- if (cap = this.rules.em.exec(src)) {
- src = src.substring(cap[0].length);
- out += this.renderer.em(this.output(cap[2] || cap[1]));
- continue;
- }
-
- // code
- if (cap = this.rules.code.exec(src)) {
- src = src.substring(cap[0].length);
- out += this.renderer.codespan(escape(cap[2], true));
- continue;
- }
-
- // text
- if (cap = this.rules.text.exec(src)) {
- src = src.substring(cap[0].length);
- out += this.renderer.text(escape(this.smartypants(cap[0])));
- continue;
- }
-
- if (src) {
- throw new
- Error('Infinite loop on byte: ' + src.charCodeAt(0));
- }
- }
-
- return out;
-};
-
-/**
- * Compile Link
- */
-
-InlineLexer.prototype.outputLink = function(cap, link) {
- var href = escape(link.href)
- , title = link.title ? escape(link.title) : null;
-
- return cap[0].charAt(0) !== '!'
- ? this.renderer.link(href, title, this.output(cap[1]))
- : this.renderer.image(href, title, escape(cap[1]));
-};
-
-/**
- * Smartypants Transformations
- */
-
-InlineLexer.prototype.smartypants = function(text) {
- if (!this.options.smartypants) return text;
- return text
- // em-dashes
- .replace(/---/g, '\u2014')
- // en-dashes
- .replace(/--/g, '\u2013')
- // opening singles
- .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018')
- // closing singles & apostrophes
- .replace(/'/g, '\u2019')
- // opening doubles
- .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c')
- // closing doubles
- .replace(/"/g, '\u201d')
- // ellipses
- .replace(/\.{3}/g, '\u2026');
-};
-
-/**
- * Mangle Links
- */
-
-InlineLexer.prototype.mangle = function(text) {
- if (!this.options.mangle) return text;
- var out = ''
- , l = text.length
- , i = 0
- , ch;
-
- for (; i < l; i++) {
- ch = text.charCodeAt(i);
- if (Math.random() > 0.5) {
- ch = 'x' + ch.toString(16);
- }
- out += '&#' + ch + ';';
- }
-
- return out;
-};
-
-/**
- * Renderer
- */
-
-function Renderer(options) {
- this.options = options || {};
-}
-
-Renderer.prototype.code = function(code, lang, escaped) {
- if (!lang) {
- return '<pre><code>'
- + (escaped ? code : escape(code, true))
- + '\n</code></pre>';
- }
-
- return '<pre><code class="'
- + this.options.langPrefix
- + escape(lang, true)
- + '">'
- + (escaped ? code : escape(code, true))
- + '\n</code></pre>\n';
-};
-
-Renderer.prototype.blockquote = function(quote) {
- return '<blockquote>\n' + quote + '</blockquote>\n';
-};
-
-Renderer.prototype.html = function(html) {
- return html;
-};
-
-Renderer.prototype.heading = function(text, level, raw) {
- return '<h'
- + level
- + ' id="'
- + this.options.headerPrefix
- + raw.toLowerCase().replace(/[^\w]+/g, '-')
- + '">'
- + text
- + '</h'
- + level
- + '>\n';
-};
-
-Renderer.prototype.hr = function() {
- return this.options.xhtml ? '<hr/>\n' : '<hr>\n';
-};
-
-Renderer.prototype.list = function(body, ordered) {
- var type = ordered ? 'ol' : 'ul';
- return '<' + type + '>\n' + body + '</' + type + '>\n';
-};
-
-Renderer.prototype.listitem = function(text) {
- return '<li>' + text + '</li>\n';
-};
-
-Renderer.prototype.paragraph = function(text) {
- return '<p>' + text + '</p>\n';
-};
-
-Renderer.prototype.table = function(header, body) {
- return '<table>\n'
- + '<thead>\n'
- + header
- + '</thead>\n'
- + '<tbody>\n'
- + body
- + '</tbody>\n'
- + '</table>\n';
-};
-
-Renderer.prototype.tablerow = function(content) {
- return '<tr>\n' + content + '</tr>\n';
-};
-
-Renderer.prototype.tablecell = function(content, flags) {
- var type = flags.header ? 'th' : 'td';
- var tag = flags.align
- ? '<' + type + ' style="text-align:' + flags.align + '">'
- : '<' + type + '>';
- return tag + content + '</' + type + '>\n';
-};
-
-// span level renderer
-Renderer.prototype.strong = function(text) {
- return '<strong>' + text + '</strong>';
-};
-
-Renderer.prototype.em = function(text) {
- return '<em>' + text + '</em>';
-};
-
-Renderer.prototype.codespan = function(text) {
- return '<code>' + text + '</code>';
-};
-
-Renderer.prototype.br = function() {
- return this.options.xhtml ? '<br/>' : '<br>';
-};
-
-Renderer.prototype.del = function(text) {
- return '<del>' + text + '</del>';
-};
-
-Renderer.prototype.link = function(href, title, text) {
- if (this.options.sanitize) {
- try {
- var prot = decodeURIComponent(unescape(href))
- .replace(/[^\w:]/g, '')
- .toLowerCase();
- } catch (e) {
- return '';
- }
- if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) {
- return '';
- }
- }
- var out = '<a href="' + href + '"';
- if (title) {
- out += ' title="' + title + '"';
- }
- out += '>' + text + '</a>';
- return out;
-};
-
-Renderer.prototype.image = function(href, title, text) {
- var out = '<img src="' + href + '" alt="' + text + '"';
- if (title) {
- out += ' title="' + title + '"';
- }
- out += this.options.xhtml ? '/>' : '>';
- return out;
-};
-
-Renderer.prototype.text = function(text) {
- return text;
-};
-
-/**
- * Parsing & Compiling
- */
-
-function Parser(options) {
- this.tokens = [];
- this.token = null;
- this.options = options || marked.defaults;
- this.options.renderer = this.options.renderer || new Renderer;
- this.renderer = this.options.renderer;
- this.renderer.options = this.options;
-}
-
-/**
- * Static Parse Method
- */
-
-Parser.parse = function(src, options, renderer) {
- var parser = new Parser(options, renderer);
- return parser.parse(src);
-};
-
-/**
- * Parse Loop
- */
-
-Parser.prototype.parse = function(src) {
- this.inline = new InlineLexer(src.links, this.options, this.renderer);
- this.tokens = src.reverse();
-
- var out = '';
- while (this.next()) {
- out += this.tok();
- }
-
- return out;
-};
-
-/**
- * Next Token
- */
-
-Parser.prototype.next = function() {
- return this.token = this.tokens.pop();
-};
-
-/**
- * Preview Next Token
- */
-
-Parser.prototype.peek = function() {
- return this.tokens[this.tokens.length - 1] || 0;
-};
-
-/**
- * Parse Text Tokens
- */
-
-Parser.prototype.parseText = function() {
- var body = this.token.text;
-
- while (this.peek().type === 'text') {
- body += '\n' + this.next().text;
- }
-
- return this.inline.output(body);
-};
-
-/**
- * Parse Current Token
- */
-
-Parser.prototype.tok = function() {
- switch (this.token.type) {
- case 'space': {
- return '';
- }
- case 'hr': {
- return this.renderer.hr();
- }
- case 'heading': {
- return this.renderer.heading(
- this.inline.output(this.token.text),
- this.token.depth,
- this.token.text);
- }
- case 'code': {
- return this.renderer.code(this.token.text,
- this.token.lang,
- this.token.escaped);
- }
- case 'table': {
- var header = ''
- , body = ''
- , i
- , row
- , cell
- , flags
- , j;
-
- // header
- cell = '';
- for (i = 0; i < this.token.header.length; i++) {
- flags = { header: true, align: this.token.align[i] };
- cell += this.renderer.tablecell(
- this.inline.output(this.token.header[i]),
- { header: true, align: this.token.align[i] }
- );
- }
- header += this.renderer.tablerow(cell);
-
- for (i = 0; i < this.token.cells.length; i++) {
- row = this.token.cells[i];
-
- cell = '';
- for (j = 0; j < row.length; j++) {
- cell += this.renderer.tablecell(
- this.inline.output(row[j]),
- { header: false, align: this.token.align[j] }
- );
- }
-
- body += this.renderer.tablerow(cell);
- }
- return this.renderer.table(header, body);
- }
- case 'blockquote_start': {
- var body = '';
-
- while (this.next().type !== 'blockquote_end') {
- body += this.tok();
- }
-
- return this.renderer.blockquote(body);
- }
- case 'list_start': {
- var body = ''
- , ordered = this.token.ordered;
-
- while (this.next().type !== 'list_end') {
- body += this.tok();
- }
-
- return this.renderer.list(body, ordered);
- }
- case 'list_item_start': {
- var body = '';
-
- while (this.next().type !== 'list_item_end') {
- body += this.token.type === 'text'
- ? this.parseText()
- : this.tok();
- }
-
- return this.renderer.listitem(body);
- }
- case 'loose_item_start': {
- var body = '';
-
- while (this.next().type !== 'list_item_end') {
- body += this.tok();
- }
-
- return this.renderer.listitem(body);
- }
- case 'html': {
- var html = !this.token.pre && !this.options.pedantic
- ? this.inline.output(this.token.text)
- : this.token.text;
- return this.renderer.html(html);
- }
- case 'paragraph': {
- return this.renderer.paragraph(this.inline.output(this.token.text));
- }
- case 'text': {
- return this.renderer.paragraph(this.parseText());
- }
- }
-};
-
-/**
- * Helpers
- */
-
-function escape(html, encode) {
- return html
- .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&amp;')
- .replace(/</g, '&lt;')
- .replace(/>/g, '&gt;')
- .replace(/"/g, '&quot;')
- .replace(/'/g, '&#39;');
-}
-
-function unescape(html) {
- return html.replace(/&([#\w]+);/g, function(_, n) {
- n = n.toLowerCase();
- if (n === 'colon') return ':';
- if (n.charAt(0) === '#') {
- return n.charAt(1) === 'x'
- ? String.fromCharCode(parseInt(n.substring(2), 16))
- : String.fromCharCode(+n.substring(1));
- }
- return '';
- });
-}
-
-function replace(regex, opt) {
- regex = regex.source;
- opt = opt || '';
- return function self(name, val) {
- if (!name) return new RegExp(regex, opt);
- val = val.source || val;
- val = val.replace(/(^|[^\[])\^/g, '$1');
- regex = regex.replace(name, val);
- return self;
- };
-}
-
-function noop() {}
-noop.exec = noop;
-
-function merge(obj) {
- var i = 1
- , target
- , key;
-
- for (; i < arguments.length; i++) {
- target = arguments[i];
- for (key in target) {
- if (Object.prototype.hasOwnProperty.call(target, key)) {
- obj[key] = target[key];
- }
- }
- }
-
- return obj;
-}
-
-
-/**
- * Marked
- */
-
-function marked(src, opt, callback) {
- if (callback || typeof opt === 'function') {
- if (!callback) {
- callback = opt;
- opt = null;
- }
-
- opt = merge({}, marked.defaults, opt || {});
-
- var highlight = opt.highlight
- , tokens
- , pending
- , i = 0;
-
- try {
- tokens = Lexer.lex(src, opt)
- } catch (e) {
- return callback(e);
- }
-
- pending = tokens.length;
-
- var done = function(err) {
- if (err) {
- opt.highlight = highlight;
- return callback(err);
- }
-
- var out;
-
- try {
- out = Parser.parse(tokens, opt);
- } catch (e) {
- err = e;
- }
-
- opt.highlight = highlight;
-
- return err
- ? callback(err)
- : callback(null, out);
- };
-
- if (!highlight || highlight.length < 3) {
- return done();
- }
-
- delete opt.highlight;
-
- if (!pending) return done();
-
- for (; i < tokens.length; i++) {
- (function(token) {
- if (token.type !== 'code') {
- return --pending || done();
- }
- return highlight(token.text, token.lang, function(err, code) {
- if (err) return done(err);
- if (code == null || code === token.text) {
- return --pending || done();
- }
- token.text = code;
- token.escaped = true;
- --pending || done();
- });
- })(tokens[i]);
- }
-
- return;
- }
- try {
- if (opt) opt = merge({}, marked.defaults, opt);
- return Parser.parse(Lexer.lex(src, opt), opt);
- } catch (e) {
- e.message += '\nPlease report this to https://github.com/chjj/marked.';
- if ((opt || marked.defaults).silent) {
- return '<p>An error occured:</p><pre>'
- + escape(e.message + '', true)
- + '</pre>';
- }
- throw e;
- }
-}
-
-/**
- * Options
- */
-
-marked.options =
-marked.setOptions = function(opt) {
- merge(marked.defaults, opt);
- return marked;
-};
-
-marked.defaults = {
- gfm: true,
- tables: true,
- breaks: false,
- pedantic: false,
- sanitize: false,
- sanitizer: null,
- mangle: true,
- smartLists: false,
- silent: false,
- highlight: null,
- langPrefix: 'lang-',
- smartypants: false,
- headerPrefix: '',
- renderer: new Renderer,
- xhtml: false
-};
-
-/**
- * Expose
- */
-
-marked.Parser = Parser;
-marked.parser = Parser.parse;
-
-marked.Renderer = Renderer;
-
-marked.Lexer = Lexer;
-marked.lexer = Lexer.lex;
-
-marked.InlineLexer = InlineLexer;
-marked.inlineLexer = InlineLexer.output;
-
-marked.parse = marked;
-
-if (typeof module !== 'undefined' && typeof exports === 'object') {
- module.exports = marked;
-} else if (typeof define === 'function' && define.amd) {
- define(function() { return marked; });
-} else {
- this.marked = marked;
-}
-
-}).call(function() {
- return this || (typeof window !== 'undefined' ? window : global);
-}());
diff --git a/web/static/js/marked/man/marked.1 b/web/static/js/marked/man/marked.1
deleted file mode 100644
index b9bdc8c21..000000000
--- a/web/static/js/marked/man/marked.1
+++ /dev/null
@@ -1,91 +0,0 @@
-.ds q \N'34'
-.TH marked 1 "2014-01-31" "v0.3.1" "marked.js"
-
-.SH NAME
-marked \- a javascript markdown parser
-
-.SH SYNOPSIS
-.B marked
-[\-o \fI<output>\fP] [\-i \fI<input>\fP] [\-\-help]
-[\-\-tokens] [\-\-pedantic] [\-\-gfm]
-[\-\-breaks] [\-\-tables] [\-\-sanitize]
-[\-\-smart\-lists] [\-\-lang\-prefix \fI<prefix>\fP]
-[\-\-no\-etc...] [\-\-silent] [\fIfilename\fP]
-
-.SH DESCRIPTION
-.B marked
-is a full-featured javascript markdown parser, built for speed. It also includes
-multiple GFM features.
-
-.SH EXAMPLES
-.TP
-cat in.md | marked > out.html
-.TP
-echo "hello *world*" | marked
-.TP
-marked \-o out.html in.md \-\-gfm
-.TP
-marked \-\-output="hello world.html" \-i in.md \-\-no-breaks
-
-.SH OPTIONS
-.TP
-.BI \-o,\ \-\-output\ [\fIoutput\fP]
-Specify file output. If none is specified, write to stdout.
-.TP
-.BI \-i,\ \-\-input\ [\fIinput\fP]
-Specify file input, otherwise use last argument as input file. If no input file
-is specified, read from stdin.
-.TP
-.BI \-t,\ \-\-tokens
-Output a token stream instead of html.
-.TP
-.BI \-\-pedantic
-Conform to obscure parts of markdown.pl as much as possible. Don't fix original
-markdown bugs.
-.TP
-.BI \-\-gfm
-Enable github flavored markdown.
-.TP
-.BI \-\-breaks
-Enable GFM line breaks. Only works with the gfm option.
-.TP
-.BI \-\-tables
-Enable GFM tables. Only works with the gfm option.
-.TP
-.BI \-\-sanitize
-Sanitize output. Ignore any HTML input.
-.TP
-.BI \-\-smart\-lists
-Use smarter list behavior than the original markdown.
-.TP
-.BI \-\-lang\-prefix\ [\fIprefix\fP]
-Set the prefix for code block classes.
-.TP
-.BI \-\-mangle
-Mangle email addresses.
-.TP
-.BI \-\-no\-sanitize,\ \-no-etc...
-The inverse of any of the marked options above.
-.TP
-.BI \-\-silent
-Silence error output.
-.TP
-.BI \-h,\ \-\-help
-Display help information.
-
-.SH CONFIGURATION
-For configuring and running programmatically.
-
-.B Example
-
- require('marked')('*foo*', { gfm: true });
-
-.SH BUGS
-Please report any bugs to https://github.com/chjj/marked.
-
-.SH LICENSE
-Copyright (c) 2011-2014, Christopher Jeffrey (MIT License).
-
-.SH "SEE ALSO"
-.BR markdown(1),
-.BR node.js(1)
diff --git a/web/static/js/marked/marked.min.js b/web/static/js/marked/marked.min.js
deleted file mode 100644
index 555c1dc1d..000000000
--- a/web/static/js/marked/marked.min.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/**
- * marked - a markdown parser
- * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed)
- * https://github.com/chjj/marked
- */
-(function(){var block={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:noop,hr:/^( *[-*_]){3,} *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/,nptable:noop,lheading:/^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/,blockquote:/^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/,list:/^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/,html:/^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/,def:/^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/,table:noop,paragraph:/^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/,text:/^[^\n]+/};block.bullet=/(?:[*+-]|\d+\.)/;block.item=/^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/;block.item=replace(block.item,"gm")(/bull/g,block.bullet)();block.list=replace(block.list)(/bull/g,block.bullet)("hr","\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))")("def","\\n+(?="+block.def.source+")")();block.blockquote=replace(block.blockquote)("def",block.def)();block._tag="(?!(?:"+"a|em|strong|small|s|cite|q|dfn|abbr|data|time|code"+"|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo"+"|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b";block.html=replace(block.html)("comment",/<!--[\s\S]*?-->/)("closed",/<(tag)[\s\S]+?<\/\1>/)("closing",/<tag(?:"[^"]*"|'[^']*'|[^'">])*?>/)(/tag/g,block._tag)();block.paragraph=replace(block.paragraph)("hr",block.hr)("heading",block.heading)("lheading",block.lheading)("blockquote",block.blockquote)("tag","<"+block._tag)("def",block.def)();block.normal=merge({},block);block.gfm=merge({},block.normal,{fences:/^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/,paragraph:/^/,heading:/^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/});block.gfm.paragraph=replace(block.paragraph)("(?!","(?!"+block.gfm.fences.source.replace("\\1","\\2")+"|"+block.list.source.replace("\\1","\\3")+"|")();block.tables=merge({},block.gfm,{nptable:/^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/,table:/^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/});function Lexer(options){this.tokens=[];this.tokens.links={};this.options=options||marked.defaults;this.rules=block.normal;if(this.options.gfm){if(this.options.tables){this.rules=block.tables}else{this.rules=block.gfm}}}Lexer.rules=block;Lexer.lex=function(src,options){var lexer=new Lexer(options);return lexer.lex(src)};Lexer.prototype.lex=function(src){src=src.replace(/\r\n|\r/g,"\n").replace(/\t/g," ").replace(/\u00a0/g," ").replace(/\u2424/g,"\n");return this.token(src,true)};Lexer.prototype.token=function(src,top,bq){var src=src.replace(/^ +$/gm,""),next,loose,cap,bull,b,item,space,i,l;while(src){if(cap=this.rules.newline.exec(src)){src=src.substring(cap[0].length);if(cap[0].length>1){this.tokens.push({type:"space"})}}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);cap=cap[0].replace(/^ {4}/gm,"");this.tokens.push({type:"code",text:!this.options.pedantic?cap.replace(/\n+$/,""):cap});continue}if(cap=this.rules.fences.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"code",lang:cap[2],text:cap[3]||""});continue}if(cap=this.rules.heading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[1].length,text:cap[2]});continue}if(top&&(cap=this.rules.nptable.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/\n$/,"").split("\n")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]="right"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]="center"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]="left"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].split(/ *\| */)}this.tokens.push(item);continue}if(cap=this.rules.lheading.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"heading",depth:cap[2]==="="?1:2,text:cap[1]});continue}if(cap=this.rules.hr.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"hr"});continue}if(cap=this.rules.blockquote.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"blockquote_start"});cap=cap[0].replace(/^ *> ?/gm,"");this.token(cap,top,true);this.tokens.push({type:"blockquote_end"});continue}if(cap=this.rules.list.exec(src)){src=src.substring(cap[0].length);bull=cap[2];this.tokens.push({type:"list_start",ordered:bull.length>1});cap=cap[0].match(this.rules.item);next=false;l=cap.length;i=0;for(;i<l;i++){item=cap[i];space=item.length;item=item.replace(/^ *([*+-]|\d+\.) +/,"");if(~item.indexOf("\n ")){space-=item.length;item=!this.options.pedantic?item.replace(new RegExp("^ {1,"+space+"}","gm"),""):item.replace(/^ {1,4}/gm,"")}if(this.options.smartLists&&i!==l-1){b=block.bullet.exec(cap[i+1])[0];if(bull!==b&&!(bull.length>1&&b.length>1)){src=cap.slice(i+1).join("\n")+src;i=l-1}}loose=next||/\n\n(?!\s*$)/.test(item);if(i!==l-1){next=item.charAt(item.length-1)==="\n";if(!loose)loose=next}this.tokens.push({type:loose?"loose_item_start":"list_item_start"});this.token(item,false,bq);this.tokens.push({type:"list_item_end"})}this.tokens.push({type:"list_end"});continue}if(cap=this.rules.html.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:this.options.sanitize?"paragraph":"html",pre:!this.options.sanitizer&&(cap[1]==="pre"||cap[1]==="script"||cap[1]==="style"),text:cap[0]});continue}if(!bq&&top&&(cap=this.rules.def.exec(src))){src=src.substring(cap[0].length);this.tokens.links[cap[1].toLowerCase()]={href:cap[2],title:cap[3]};continue}if(top&&(cap=this.rules.table.exec(src))){src=src.substring(cap[0].length);item={type:"table",header:cap[1].replace(/^ *| *\| *$/g,"").split(/ *\| */),align:cap[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:cap[3].replace(/(?: *\| *)?\n$/,"").split("\n")};for(i=0;i<item.align.length;i++){if(/^ *-+: *$/.test(item.align[i])){item.align[i]="right"}else if(/^ *:-+: *$/.test(item.align[i])){item.align[i]="center"}else if(/^ *:-+ *$/.test(item.align[i])){item.align[i]="left"}else{item.align[i]=null}}for(i=0;i<item.cells.length;i++){item.cells[i]=item.cells[i].replace(/^ *\| *| *\| *$/g,"").split(/ *\| */)}this.tokens.push(item);continue}if(top&&(cap=this.rules.paragraph.exec(src))){src=src.substring(cap[0].length);this.tokens.push({type:"paragraph",text:cap[1].charAt(cap[1].length-1)==="\n"?cap[1].slice(0,-1):cap[1]});continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);this.tokens.push({type:"text",text:cap[0]});continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return this.tokens};var inline={escape:/^\\([\\`*{}\[\]()#+\-.!_>])/,autolink:/^<([^ >]+(@|:\/)[^ >]+)>/,url:noop,tag:/^<!--[\s\S]*?-->|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/,link:/^!?\[(inside)\]\(href\)/,reflink:/^!?\[(inside)\]\s*\[([^\]]*)\]/,nolink:/^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/,strong:/^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/,em:/^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/,code:/^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/,br:/^ {2,}\n(?!\s*$)/,del:noop,text:/^[\s\S]+?(?=[\\<!\[_*`]| {2,}\n|$)/};inline._inside=/(?:\[[^\]]*\]|[^\[\]]|\](?=[^\[]*\]))*/;inline._href=/\s*<?([\s\S]*?)>?(?:\s+['"]([\s\S]*?)['"])?\s*/;inline.link=replace(inline.link)("inside",inline._inside)("href",inline._href)();inline.reflink=replace(inline.reflink)("inside",inline._inside)();inline.normal=merge({},inline);inline.pedantic=merge({},inline.normal,{strong:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,em:/^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/});inline.gfm=merge({},inline.normal,{escape:replace(inline.escape)("])","~|])")(),url:/^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/,del:/^~~(?=\S)([\s\S]*?\S)~~/,text:replace(inline.text)("]|","~]|")("|","|https?://|")()});inline.breaks=merge({},inline.gfm,{br:replace(inline.br)("{2,}","*")(),text:replace(inline.gfm.text)("{2,}","*")()});function InlineLexer(links,options){this.options=options||marked.defaults;this.links=links;this.rules=inline.normal;this.renderer=this.options.renderer||new Renderer;this.renderer.options=this.options;if(!this.links){throw new Error("Tokens array requires a `links` property.")}if(this.options.gfm){if(this.options.breaks){this.rules=inline.breaks}else{this.rules=inline.gfm}}else if(this.options.pedantic){this.rules=inline.pedantic}}InlineLexer.rules=inline;InlineLexer.output=function(src,links,options){var inline=new InlineLexer(links,options);return inline.output(src)};InlineLexer.prototype.output=function(src){var out="",link,text,href,cap;while(src){if(cap=this.rules.escape.exec(src)){src=src.substring(cap[0].length);out+=cap[1];continue}if(cap=this.rules.autolink.exec(src)){src=src.substring(cap[0].length);if(cap[2]==="@"){text=cap[1].charAt(6)===":"?this.mangle(cap[1].substring(7)):this.mangle(cap[1]);href=this.mangle("mailto:")+text}else{text=escape(cap[1]);href=text}out+=this.renderer.link(href,null,text);continue}if(!this.inLink&&(cap=this.rules.url.exec(src))){src=src.substring(cap[0].length);text=escape(cap[1]);href=text;out+=this.renderer.link(href,null,text);continue}if(cap=this.rules.tag.exec(src)){if(!this.inLink&&/^<a /i.test(cap[0])){this.inLink=true}else if(this.inLink&&/^<\/a>/i.test(cap[0])){this.inLink=false}src=src.substring(cap[0].length);out+=this.options.sanitize?this.options.sanitizer?this.options.sanitizer(cap[0]):escape(cap[0]):cap[0];continue}if(cap=this.rules.link.exec(src)){src=src.substring(cap[0].length);this.inLink=true;out+=this.outputLink(cap,{href:cap[2],title:cap[3]});this.inLink=false;continue}if((cap=this.rules.reflink.exec(src))||(cap=this.rules.nolink.exec(src))){src=src.substring(cap[0].length);link=(cap[2]||cap[1]).replace(/\s+/g," ");link=this.links[link.toLowerCase()];if(!link||!link.href){out+=cap[0].charAt(0);src=cap[0].substring(1)+src;continue}this.inLink=true;out+=this.outputLink(cap,link);this.inLink=false;continue}if(cap=this.rules.strong.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.strong(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.em.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.em(this.output(cap[2]||cap[1]));continue}if(cap=this.rules.code.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.codespan(escape(cap[2],true));continue}if(cap=this.rules.br.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.br();continue}if(cap=this.rules.del.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.del(this.output(cap[1]));continue}if(cap=this.rules.text.exec(src)){src=src.substring(cap[0].length);out+=this.renderer.text(escape(this.smartypants(cap[0])));continue}if(src){throw new Error("Infinite loop on byte: "+src.charCodeAt(0))}}return out};InlineLexer.prototype.outputLink=function(cap,link){var href=escape(link.href),title=link.title?escape(link.title):null;return cap[0].charAt(0)!=="!"?this.renderer.link(href,title,this.output(cap[1])):this.renderer.image(href,title,escape(cap[1]))};InlineLexer.prototype.smartypants=function(text){if(!this.options.smartypants)return text;return text.replace(/---/g,"—").replace(/--/g,"–").replace(/(^|[-\u2014/(\[{"\s])'/g,"$1‘").replace(/'/g,"’").replace(/(^|[-\u2014/(\[{\u2018\s])"/g,"$1“").replace(/"/g,"”").replace(/\.{3}/g,"…")};InlineLexer.prototype.mangle=function(text){if(!this.options.mangle)return text;var out="",l=text.length,i=0,ch;for(;i<l;i++){ch=text.charCodeAt(i);if(Math.random()>.5){ch="x"+ch.toString(16)}out+="&#"+ch+";"}return out};function Renderer(options){this.options=options||{}}Renderer.prototype.code=function(code,lang,escaped){if(this.options.highlight){var out=this.options.highlight(code,lang);if(out!=null&&out!==code){escaped=true;code=out}}if(!lang){return"<pre><code>"+(escaped?code:escape(code,true))+"\n</code></pre>"}return'<pre><code class="'+this.options.langPrefix+escape(lang,true)+'">'+(escaped?code:escape(code,true))+"\n</code></pre>\n"};Renderer.prototype.blockquote=function(quote){return"<blockquote>\n"+quote+"</blockquote>\n"};Renderer.prototype.html=function(html){return html};Renderer.prototype.heading=function(text,level,raw){return"<h"+level+' id="'+this.options.headerPrefix+raw.toLowerCase().replace(/[^\w]+/g,"-")+'">'+text+"</h"+level+">\n"};Renderer.prototype.hr=function(){return this.options.xhtml?"<hr/>\n":"<hr>\n"};Renderer.prototype.list=function(body,ordered){var type=ordered?"ol":"ul";return"<"+type+">\n"+body+"</"+type+">\n"};Renderer.prototype.listitem=function(text){return"<li>"+text+"</li>\n"};Renderer.prototype.paragraph=function(text){return"<p>"+text+"</p>\n"};Renderer.prototype.table=function(header,body){return"<table>\n"+"<thead>\n"+header+"</thead>\n"+"<tbody>\n"+body+"</tbody>\n"+"</table>\n"};Renderer.prototype.tablerow=function(content){return"<tr>\n"+content+"</tr>\n"};Renderer.prototype.tablecell=function(content,flags){var type=flags.header?"th":"td";var tag=flags.align?"<"+type+' style="text-align:'+flags.align+'">':"<"+type+">";return tag+content+"</"+type+">\n"};Renderer.prototype.strong=function(text){return"<strong>"+text+"</strong>"};Renderer.prototype.em=function(text){return"<em>"+text+"</em>"};Renderer.prototype.codespan=function(text){return"<code>"+text+"</code>"};Renderer.prototype.br=function(){return this.options.xhtml?"<br/>":"<br>"};Renderer.prototype.del=function(text){return"<del>"+text+"</del>"};Renderer.prototype.link=function(href,title,text){if(this.options.sanitize){try{var prot=decodeURIComponent(unescape(href)).replace(/[^\w:]/g,"").toLowerCase()}catch(e){return""}if(prot.indexOf("javascript:")===0||prot.indexOf("vbscript:")===0){return""}}var out='<a href="'+href+'"';if(title){out+=' title="'+title+'"'}out+=">"+text+"</a>";return out};Renderer.prototype.image=function(href,title,text){var out='<img src="'+href+'" alt="'+text+'"';if(title){out+=' title="'+title+'"'}out+=this.options.xhtml?"/>":">";return out};Renderer.prototype.text=function(text){return text};function Parser(options){this.tokens=[];this.token=null;this.options=options||marked.defaults;this.options.renderer=this.options.renderer||new Renderer;this.renderer=this.options.renderer;this.renderer.options=this.options}Parser.parse=function(src,options,renderer){var parser=new Parser(options,renderer);return parser.parse(src)};Parser.prototype.parse=function(src){this.inline=new InlineLexer(src.links,this.options,this.renderer);this.tokens=src.reverse();var out="";while(this.next()){out+=this.tok()}return out};Parser.prototype.next=function(){return this.token=this.tokens.pop()};Parser.prototype.peek=function(){return this.tokens[this.tokens.length-1]||0};Parser.prototype.parseText=function(){var body=this.token.text;while(this.peek().type==="text"){body+="\n"+this.next().text}return this.inline.output(body)};Parser.prototype.tok=function(){switch(this.token.type){case"space":{return""}case"hr":{return this.renderer.hr()}case"heading":{return this.renderer.heading(this.inline.output(this.token.text),this.token.depth,this.token.text)}case"code":{return this.renderer.code(this.token.text,this.token.lang,this.token.escaped)}case"table":{var header="",body="",i,row,cell,flags,j;cell="";for(i=0;i<this.token.header.length;i++){flags={header:true,align:this.token.align[i]};cell+=this.renderer.tablecell(this.inline.output(this.token.header[i]),{header:true,align:this.token.align[i]})}header+=this.renderer.tablerow(cell);for(i=0;i<this.token.cells.length;i++){row=this.token.cells[i];cell="";for(j=0;j<row.length;j++){cell+=this.renderer.tablecell(this.inline.output(row[j]),{header:false,align:this.token.align[j]})}body+=this.renderer.tablerow(cell)}return this.renderer.table(header,body)}case"blockquote_start":{var body="";while(this.next().type!=="blockquote_end"){body+=this.tok()}return this.renderer.blockquote(body)}case"list_start":{var body="",ordered=this.token.ordered;while(this.next().type!=="list_end"){body+=this.tok()}return this.renderer.list(body,ordered)}case"list_item_start":{var body="";while(this.next().type!=="list_item_end"){body+=this.token.type==="text"?this.parseText():this.tok()}return this.renderer.listitem(body)}case"loose_item_start":{var body="";while(this.next().type!=="list_item_end"){body+=this.tok()}return this.renderer.listitem(body)}case"html":{var html=!this.token.pre&&!this.options.pedantic?this.inline.output(this.token.text):this.token.text;return this.renderer.html(html)}case"paragraph":{return this.renderer.paragraph(this.inline.output(this.token.text))}case"text":{return this.renderer.paragraph(this.parseText())}}};function escape(html,encode){return html.replace(!encode?/&(?!#?\w+;)/g:/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function unescape(html){return html.replace(/&([#\w]+);/g,function(_,n){n=n.toLowerCase();if(n==="colon")return":";if(n.charAt(0)==="#"){return n.charAt(1)==="x"?String.fromCharCode(parseInt(n.substring(2),16)):String.fromCharCode(+n.substring(1))}return""})}function replace(regex,opt){regex=regex.source;opt=opt||"";return function self(name,val){if(!name)return new RegExp(regex,opt);val=val.source||val;val=val.replace(/(^|[^\[])\^/g,"$1");regex=regex.replace(name,val);return self}}function noop(){}noop.exec=noop;function merge(obj){var i=1,target,key;for(;i<arguments.length;i++){target=arguments[i];for(key in target){if(Object.prototype.hasOwnProperty.call(target,key)){obj[key]=target[key]}}}return obj}function marked(src,opt,callback){if(callback||typeof opt==="function"){if(!callback){callback=opt;opt=null}opt=merge({},marked.defaults,opt||{});var highlight=opt.highlight,tokens,pending,i=0;try{tokens=Lexer.lex(src,opt)}catch(e){return callback(e)}pending=tokens.length;var done=function(err){if(err){opt.highlight=highlight;return callback(err)}var out;try{out=Parser.parse(tokens,opt)}catch(e){err=e}opt.highlight=highlight;return err?callback(err):callback(null,out)};if(!highlight||highlight.length<3){return done()}delete opt.highlight;if(!pending)return done();for(;i<tokens.length;i++){(function(token){if(token.type!=="code"){return--pending||done()}return highlight(token.text,token.lang,function(err,code){if(err)return done(err);if(code==null||code===token.text){return--pending||done()}token.text=code;token.escaped=true;--pending||done()})})(tokens[i])}return}try{if(opt)opt=merge({},marked.defaults,opt);return Parser.parse(Lexer.lex(src,opt),opt)}catch(e){e.message+="\nPlease report this to https://github.com/chjj/marked.";if((opt||marked.defaults).silent){return"<p>An error occured:</p><pre>"+escape(e.message+"",true)+"</pre>"}throw e}}marked.options=marked.setOptions=function(opt){merge(marked.defaults,opt);return marked};marked.defaults={gfm:true,tables:true,breaks:false,pedantic:false,sanitize:false,sanitizer:null,mangle:true,smartLists:false,silent:false,highlight:null,langPrefix:"lang-",smartypants:false,headerPrefix:"",renderer:new Renderer,xhtml:false};marked.Parser=Parser;marked.parser=Parser.parse;marked.Renderer=Renderer;marked.Lexer=Lexer;marked.lexer=Lexer.lex;marked.InlineLexer=InlineLexer;marked.inlineLexer=InlineLexer.output;marked.parse=marked;if(typeof module!=="undefined"&&typeof exports==="object"){module.exports=marked}else if(typeof define==="function"&&define.amd){define(function(){return marked})}else{this.marked=marked}}).call(function(){return this||(typeof window!=="undefined"?window:global)}()); \ No newline at end of file
diff --git a/web/templates/channel.html b/web/templates/channel.html
index da6fed97d..9bfd1fa35 100644
--- a/web/templates/channel.html
+++ b/web/templates/channel.html
@@ -14,6 +14,7 @@
<div id="navbar"></div>
</div>
<div class="row main">
+ <div id="file_upload_overlay"></div>
<div id="app-content" class="app__content">
<div id="channel-header"></div>
<div id="post-list"></div>
diff --git a/web/templates/head.html b/web/templates/head.html
index 7a7d4fe8e..dd5e9f46e 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -32,6 +32,8 @@
<script src="/static/js/perfect-scrollbar-0.6.3.jquery.js"></script>
+ <script src="/static/js/jquery-dragster/jquery.dragster.js"></script>
+
<script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['annotationchart']}]}"></script>
<script type="text/javascript" src="https://cloudfront.loggly.com/js/loggly.tracker.js" async></script>