Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Maths support #3251

Closed
wants to merge 28 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
755bfaf
let spans have the katex class
thosgood Jul 22, 2019
affc69e
last-minute KaTeX rendering
thosgood Jul 23, 2019
36aba90
regex fun
thosgood Jul 23, 2019
7fbbd79
broken some things, added others
thosgood Jul 23, 2019
38ae1fc
katex rendering settings sort of work (but not on a room-by-room basis)
thosgood Jul 23, 2019
986498d
css is a bit better now
thosgood Jul 23, 2019
b3d627b
"one" thing missing now... sending with org.matrix.custom.html format…
thosgood Jul 23, 2019
154a4b1
ok, this works, but just need to default it to being off
thosgood Jul 24, 2019
50c31a1
new math.png
thosgood Jul 24, 2019
1538b42
message composer now remember math state (and defaults to off) (almost)
thosgood Jul 24, 2019
c20fba8
ok fixed that problem
thosgood Jul 24, 2019
12ccefc
for some reason, per-room KaTeX settings don't work...
thosgood Jul 24, 2019
f23aedb
cannot get room-account settings to work
thosgood Jul 24, 2019
70ffd6b
removed per-room-account katex setting
thosgood Jul 27, 2019
a67540b
send math in matrix tags
thosgood Jul 28, 2019
ad86a8f
sending math now plays nicely with markdown
thosgood Jul 28, 2019
f57e18b
fixed logic + almost everything works
thosgood Jul 28, 2019
306a9ed
can now send math without markdown enabled
thosgood Jul 28, 2019
ffb596f
fixed math mode not being enabled when markdown on
thosgood Jul 28, 2019
ff36572
fixed math.svg
thosgood Aug 9, 2019
46084df
removed small unnecessary changes
thosgood Aug 18, 2019
18b6990
lazy regexing
thosgood Aug 21, 2019
07eab84
if KaTeX rendering is turned off, then display as code
thosgood Aug 22, 2019
2dd355c
fixed conflict with Settings.js
thosgood Aug 28, 2019
42e562f
Following MSC2191
thosgood Sep 19, 2019
5cbf349
factored out the fallback rendering ready for whatever solution we find
thosgood Sep 19, 2019
98ac7d5
added display option
thosgood Sep 20, 2019
b148fff
removed fallback factoring
thosgood Sep 21, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -80,6 +80,7 @@
"highlight.js": "^9.15.8",
"is-ip": "^2.0.0",
"isomorphic-fetch": "^2.2.1",
"katex": "^0.10.2",
"linkifyjs": "^2.1.6",
"lodash": "^4.17.14",
"lolex": "2.3.2",
Expand Down
21 changes: 21 additions & 0 deletions res/css/views/rooms/_MessageComposer.scss
Expand Up @@ -265,6 +265,14 @@ limitations under the License.
padding: 4px 4px 4px 0;
}

.mx_MessageComposer_input_mathIndicator {
position: absolute;
height: 10px;
width: 12px;
margin-left: 20px;
padding: 4px 4px 4px 0;
}

.mx_MessageComposer_formatbar_markdown,
.mx_MessageComposer_input_markdownIndicator {
cursor: pointer;
Expand All @@ -279,6 +287,19 @@ limitations under the License.
}
}

.mx_MessageComposer_input_mathIndicator {
cursor: pointer;
mask-image: url('$(res)/img/math.png');
mask-size: contain;
mask-position: center;
mask-repeat: no-repeat;
background-color: $composer-button-color;

&.mx_MessageComposer_mathDisabled {
opacity: 0.2;
}
}

.mx_MatrixChat_useCompactLayout {
.mx_MessageComposer_input {
min-height: 50px;
Expand Down
Binary file added res/img/math.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions res/img/math.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions src/Analytics.js
Expand Up @@ -239,6 +239,11 @@ class Analytics {
this._setVisitVariable('RTE: Uses Richtext Mode', state ? 'on' : 'off');
}

setMathMode(state) {
if (this.disabled) return;
this._setVisitVariable('Uses Math Mode', state ? 'on' : 'off');
}

setBreadcrumbs(state) {
if (this.disabled) return;
this._setVisitVariable('Breadcrumbs', state ? 'enabled' : 'disabled');
Expand Down
24 changes: 23 additions & 1 deletion src/HtmlUtils.js
Expand Up @@ -30,6 +30,7 @@ import _linkifyString from 'linkifyjs/string';
import classNames from 'classnames';
import MatrixClientPeg from './MatrixClientPeg';
import url from 'url';
import katex from 'katex';

import EMOJIBASE from 'emojibase-data/en/compact.json';
import EMOJIBASE_REGEX from 'emojibase-regex';
Expand Down Expand Up @@ -242,7 +243,7 @@ const transformTags = { // custom to matrix
}

return { tagName, attribs };
},
}
};

const sanitizeHtmlParams = {
Expand All @@ -252,6 +253,7 @@ const sanitizeHtmlParams = {
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'sup', 'sub',
'nl', 'li', 'b', 'i', 'u', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div',
'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'span', 'img',
'matrix-math', 'matrix-math-display'
],
allowedAttributes: {
// custom ones first:
Expand Down Expand Up @@ -401,6 +403,7 @@ class TextHighlighter extends BaseHighlighter {
* opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing
* opts.returnString: return an HTML string rather than JSX elements
* opts.forComposerQuote: optional param to lessen the url rewriting done by sanitization, for quoting into composer
* opts.renderKatex: optional argument to render mathematics using KaTeX
*/
export function bodyToHtml(content, highlights, opts={}) {
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
Expand Down Expand Up @@ -478,6 +481,25 @@ export function bodyToHtml(content, highlights, opts={}) {
'markdown-body': isHtmlMessage && !emojiBody,
});

if (opts.renderKatex) {
const mathDelimiters = [
{ left: "<matrix-math-display>", right: "<\\/matrix-math-display>", display: true },
{ left: "<matrix-math>", right: "<\\/matrix-math>", display: false }
];

if ("undefined" != typeof safeBody) {
mathDelimiters.forEach(function (d) {
var reg = RegExp(d.left + "(.*)" + d.right, "g");
safeBody = safeBody.replace(reg, function(match, p1) {
return katex.renderToString(p1, {
throwOnError: false,
displayMode: d.display
})
});
});
};
};

return isDisplayedWithHtml ?
<span key="body" className={className} dangerouslySetInnerHTML={{ __html: safeBody }} dir="auto" /> :
<span key="body" className={className} dir="auto">{ strippedBody }</span>;
Expand Down
2 changes: 1 addition & 1 deletion src/components/structures/RoomView.js
Expand Up @@ -777,7 +777,7 @@ module.exports = React.createClass({
if ((type === "org.matrix.preview_urls" || type === "im.vector.web.settings") && this.state.room) {
// non-e2ee url previews are stored in legacy event type `org.matrix.room.preview_urls`
this._updatePreviewUrlVisibility(this.state.room);
}
};
},

onRoomAccountData: function(event, room) {
Expand Down
2 changes: 2 additions & 0 deletions src/components/views/messages/TextualBody.js
Expand Up @@ -390,8 +390,10 @@ module.exports = React.createClass({
const content = mxEvent.getContent();

const stripReply = ReplyThread.getParentEventId(mxEvent);

let body = HtmlUtils.bodyToHtml(content, this.props.highlights, {
disableBigEmoji: content.msgtype === "m.emote" || !SettingsStore.getValue('TextualBody.enableBigEmoji'),
renderKatex: SettingsStore.getValue("katexRendering"),
// Part of Replies fallback support
stripReplyFallback: stripReply,
});
Expand Down
108 changes: 108 additions & 0 deletions src/components/views/room_settings/KatexRenderingSettings.js
@@ -0,0 +1,108 @@
/*
Copyright 2016 OpenMarket Ltd
Copyright 2017 Travis Ralston
Copyright 2018-2019 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

const React = require('react');
import PropTypes from 'prop-types';
const sdk = require("../../../index");
import { _t, _td } from '../../../languageHandler';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import dis from "../../../dispatcher";
import MatrixClientPeg from "../../../MatrixClientPeg";


module.exports = React.createClass({
displayName: 'KatexRenderingSettings',

propTypes: {
room: PropTypes.object,
},

_onClickUserSettings: (e) => {
e.preventDefault();
e.stopPropagation();
dis.dispatch({action: 'view_user_settings'});
},

render: function() {
const SettingsFlag = sdk.getComponent("elements.SettingsFlag");
const roomId = this.props.room.roomId;
const isEncrypted = MatrixClientPeg.get().isRoomEncrypted(roomId);

let katexForRoomAccount = null;
let katexForAccount = null;
let katexForRoom = null;

if (!isEncrypted) {
// Only show account setting state and room state setting state in non-e2ee rooms where they apply
const accountEnabled = SettingsStore.getValueAt(SettingLevel.ACCOUNT, "katexRendering");
if (accountEnabled) {
katexForAccount = (
_t("You have <a>enabled</a> KaTeX rendering by default.", {}, {
'a': (sub)=><a onClick={this._onClickUserSettings} href=''>{ sub }</a>,
})
);
} else if (!accountEnabled) {
katexForAccount = (
_t("You have <a>disabled</a> KaTeX rendering by default.", {}, {
'a': (sub)=><a onClick={this._onClickUserSettings} href=''>{ sub }</a>,
})
);
}

if (SettingsStore.canSetValue("katexRendering", roomId, "room")) {
katexForRoom = (
<label>
<SettingsFlag name="katexRendering"
level={SettingLevel.ROOM}
roomId={roomId}
isExplicit={true} />
</label>
);
} else {
let str = _td("KaTeX rendering is enabled by default for participants in this room.");
if (!SettingsStore.getValueAt(SettingLevel.ROOM, "katexRendering", roomId, /*explicit=*/true)) {
str = _td("KaTeX rendering is disabled by default for participants in this room.");
}
katexForRoom = (<label>{ _t(str) }</label>);
}
} else {
katexForAccount = (
_t("In encrypted rooms, like this one, KaTeX rendering is disabled by default.")
);
}

katexForRoomAccount = (
<SettingsFlag name={'katexRendering'}
level={SettingLevel.ROOM_ACCOUNT}
roomId={roomId} />
);

return (
<div>
<div className='mx_SettingsTab_subsectionText'>
{ _t('When someone types mathematics using \$ signs, KaTeX can render the contents.') }
</div>
<div className='mx_SettingsTab_subsectionText'>
{ katexForAccount }
</div>
{ katexForRoom }
<label>{ katexForRoomAccount }</label>
</div>
);
},
});
7 changes: 7 additions & 0 deletions src/components/views/rooms/MessageComposer.js
Expand Up @@ -198,6 +198,7 @@ export default class MessageComposer extends React.Component {
this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
this.onToggleMathClicked = this.onToggleMathClicked.bind(this);
this.onInputStateChanged = this.onInputStateChanged.bind(this);
this.onEvent = this.onEvent.bind(this);
this._onRoomStateEvents = this._onRoomStateEvents.bind(this);
Expand All @@ -211,6 +212,7 @@ export default class MessageComposer extends React.Component {
marks: [],
blockType: null,
isRichTextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
isMathEnabled: SettingsStore.getValue('MessageComposerInput.isMathEnabled')
},
showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
Expand Down Expand Up @@ -311,6 +313,11 @@ export default class MessageComposer extends React.Component {
this.messageComposerInput.enableRichtext(!this.state.inputState.isRichTextEnabled);
}

onToggleMathClicked(e) {
e.preventDefault(); // don't steal focus from the editor!
this.messageComposerInput.enableMath(!this.state.inputState.isMathEnabled);
}

_onTombstoneClick(ev) {
ev.preventDefault();

Expand Down