From 15e5dacc511a3e8ef6e8e560f3fdd2b0acada4e6 Mon Sep 17 00:00:00 2001 From: or-else Date: Tue, 4 Sep 2018 16:20:34 +0300 Subject: [PATCH] adding desktop alerts, React and js updates --- README.md | 10 +++- index.js | 164 ++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 112 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 8d546e6d..6a207176 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,18 @@ and [icons](https://google.github.io/material-design-icons/#icon-font-for-the-we Although the app is generally usable, keep in mind that this is work in progress. Some bugs probably exist. The app was tested in the latest Chrome & Firefox only. NPM [is available](https://www.npmjs.com/package/tinode-webapp). -Try possibly newer or older version live at https://api.tinode.co/. Login as one of `alice`, `bob`, `carol`, `dave`, `frank`. Password is `123`, e.g. password for `alice` is `alice123`. You can discover other users by email or phone by prefixing them with `email:` or `tel:` respectively. Emails are `@example.com`, e.g. `alice@example.com`, phones are `17025550001` through `17025550009`. The demo is reset every night at 2:15am Pacific time. - -License: [Apache 2.0](http://www.apache.org/licenses/LICENSE-2.0) +Try possibly newer or older version live at https://api.tinode.co/. For login and other instructions [see here](https://github.com/tinode/chat/#demo). Sample avatar photos from https://www.pexels.com/ under [CC0 license](https://www.pexels.com/photo-license/). Background pattern from http://subtlepatterns.com/, commercial and non-commercial use allowed with attribution. +## Getting support + +* Read [client-side](http://tinode.github.io/js-api/) and [server-side](https://github.com/tinode/chat/blob/master/docs/API.md) API documentation. +* For support, general questions, discussions post to [https://groups.google.com/d/forum/tinode](https://groups.google.com/d/forum/tinode). +* For bugs and feature requests [open an issue](https://github.com/tinode/webchat/issues/new). + ## Missing functionality * Previews not generated for videos, audio, links or docs. diff --git a/index.js b/index.js index 265d7d4e..409041fe 100644 --- a/index.js +++ b/index.js @@ -384,15 +384,25 @@ function setUrlTopic(hash, topic) { // Detect server address from the URL function detectServerAddress() { - var host = DEFAULT_HOST; - if (window.location.protocol === 'file:' || window.location.hostname === 'localhost') { - host = KNOWN_HOSTS.local; - } else if (window.location.hostname) { - host = window.location.hostname + (window.location.port ? ':' + window.location.port : ''); + let host = DEFAULT_HOST; + if (typeof window.location == 'object') { + if (window.location.protocol == 'file:' || window.location.hostname == 'localhost') { + host = KNOWN_HOSTS.local; + } else if (window.location.hostname) { + host = window.location.hostname + (window.location.port ? ':' + window.location.port : ''); + } } return host; } +// Detect if the page is served over HTTPS. +function isSecureConnection() { + if (typeof window.location == 'object') { + return window.location.protocol == 'https:'; + } + return false; +} + // Create VCard which represents topic 'public' info function vcard(fn, imageDataUrl) { var card = null; @@ -475,7 +485,7 @@ function deleteMessages(all, hard, params, errorHandler) { var promise = all ? topic.delMessagesAll(hard) : topic.delMessagesList([params.seq], hard); - promise.catch(function(err) { + promise.catch((err) => { if (errorHandler) { errorHandler(err.message, "err"); } @@ -548,7 +558,7 @@ class ContextMenu extends React.Component { this.toggle(false); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { this.toggle(nextProps.visible); } @@ -581,7 +591,7 @@ class ContextMenu extends React.Component { e.preventDefault(); e.stopPropagation(); this.props.hide(); - var item = this.props.items[e.currentTarget.dataset.id]; + let item = this.props.items[e.currentTarget.dataset.id]; item.handler(this.props.params, this.props.onError); } @@ -590,16 +600,15 @@ class ContextMenu extends React.Component { return null; } - var count = 0; - var instance = this; - var menu = []; - this.props.items.map(function(item) { + let count = 0; + let menu = []; + this.props.items.map((item) => { if (item && item.title) { menu.push( item.title === "-" ?
  • : -
  • {item.title}
  • +
  • {item.title}
  • ); } count++; @@ -661,7 +670,7 @@ class MoreButton extends React.PureComponent { } handleToggle() { - var open = !this.state.open; + let open = !this.state.open; this.setState({open: open}); if (this.props.onToggle) { this.props.onToggle(open); @@ -679,11 +688,11 @@ class MoreButton extends React.PureComponent { /* BEGIN Lettertile: Avatar box: either a bitmap or a letter tile or a stock icon. */ class LetterTile extends React.PureComponent { render() { - var avatar; + let avatar; if (this.props.avatar === true) { if (this.props.topic && this.props.title && this.props.title.trim()) { - var letter = this.props.title.trim().charAt(0); - var color = "lettertile dark-color" + (Math.abs(stringHash(this.props.topic)) % 16); + let letter = this.props.title.trim().charAt(0); + let color = "lettertile dark-color" + (Math.abs(stringHash(this.props.topic)) % 16); avatar = (
    {letter}
    ) } else { avatar = (Tinode.topicType(this.props.topic) === "grp") ? @@ -708,6 +717,7 @@ class InPlaceEdit extends React.Component { this.state = { active: props.state, + initialText: props.text || "", text: props.text || "" }; @@ -717,12 +727,16 @@ class InPlaceEdit extends React.Component { this.handleEditingFinished = this.handleEditingFinished.bind(this); } - componentWillReceiveProps(nextProps) { + static getDerivedStateFromProps(nextProps, prevState) { // If text has changed while in read mode, update text and discard changes. // Ignore update if in edit mode. - if (this.props.text != nextProps.text && !this.state.active) { - this.setState({text: nextProps.text || ""}); + if (prevState.initialText != nextProps.text && !prevState.active) { + return { + initialText: nextProps.text || "", + text: nextProps.text || "" + }; } + return null; } handeTextChange(e) { @@ -875,8 +889,8 @@ class PermissionsEditor extends React.Component { } handleChange(val) { - var mode = this.state.mode; - var idx = mode.indexOf(val); + let mode = this.state.mode; + let idx = mode.indexOf(val); if (idx == -1) { mode += val; } else { @@ -901,8 +915,8 @@ class PermissionsEditor extends React.Component { } render() { - var all = 'JRWPASDO'; - var names = { + const all = 'JRWPASDO'; + const names = { 'J': 'Join (J)', 'R': 'Read (R)', 'W': 'Write (W)', @@ -913,12 +927,12 @@ class PermissionsEditor extends React.Component { 'O': 'Owner (O)' }; - var skip = this.props.skip || ""; - var mode = this.state.mode; - var compare = (this.props.compare || "").replace("N", ""); - var items = []; - for (var i=0; i= 0 && mode.indexOf(c) < 0) { // Permission is marked as inactive: hide unchecked permissions, disable checked permissions continue; @@ -987,7 +1001,7 @@ class ChipInput extends React.Component { this.handleKeyDown = this.handleKeyDown.bind(this); } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { this.setState({ sortedChips: ChipInput.sortChips(nextProps.chips, nextProps.required), chipIndex: ChipInput.indexChips(nextProps.chips) @@ -1149,8 +1163,10 @@ class GroupSubs extends React.Component { }; } - componentWillReceiveProps(nextProps) { - this.setState({onlineSubs: nextProps.subscribers ? nextProps.subscribers : []}); + static getDerivedStateFromProps(nextProps, prevState) { + return { + onlineSubs: nextProps.subscribers ? nextProps.subscribers : [] + }; } render() { @@ -1373,10 +1389,13 @@ class AvatarUpload extends React.Component { this.handleFileUpload = this.handleFileUpload.bind(this); } - componentWillReceiveProps(nextProps) { - if (this.props.avatar != nextProps.avatar) { - this.setState({dataUrl: nextProps.avatar}); + static getDerivedStateFromProps(nextProps, prevState) { + if (prevState.dataUrl != nextProps.avatar) { + return { + dataUrl: nextProps.avatar + }; } + return null; } handleFileUpload(e) { @@ -1430,21 +1449,30 @@ class SettingsView extends React.PureComponent { this.state = { transport: props.transport || "def", - messageSounds: props.messageSounds + messageSounds: props.messageSounds, + desktopAlerts: props.desktopAlerts && props.desktopAlertsEnabled }; this.handleSubmit = this.handleSubmit.bind(this); - this.handleMessageSoundsToggle = this.handleMessageSoundsToggle.bind(this); + this.handleCheckboxClick = this.handleCheckboxClick.bind(this); this.handleTransportSelected = this.handleTransportSelected.bind(this); } handleSubmit(e) { e.preventDefault(); - this.props.onUpdate(null, this.state.transport, this.state.messageSounds); + this.props.onUpdate({ + transport: this.state.transport, + messageSounds: this.state.messageSounds, + desktopAlerts: this.state.desktopAlerts + }); } - handleMessageSoundsToggle(unused, checked) { - this.setState({messageSounds: checked}); + handleCheckboxClick(what, checked) { + if (what == "sound") { + this.setState({messageSounds: checked}); + } else if (what == "alert") { + this.setState({desktopAlerts: checked}); + } } handleTransportSelected(e) { @@ -1471,9 +1499,15 @@ class SettingsView extends React.PureComponent {
    - + onChange={this.handleCheckboxClick} /> +
    +
    + +
    @@ -1615,6 +1649,8 @@ class SidepanelView extends React.Component { : @@ -1672,8 +1708,10 @@ class ErrorPanel extends React.PureComponent { this.hide = this.hide.bind(this); } - componentWillReceiveProps(nextProps) { - this.setState({show: !(!nextProps.level)}); + static getDerivedStateFromProps(nextProps, prevState) { + return { + show: !(!nextProps.level) + }; } hide() { @@ -1852,8 +1890,8 @@ class TagManager extends React.Component { this.handleCancel = this.handleCancel.bind(this); } - componentWillReceiveProps(nextProps) { - this.setState({tags: nextProps.tags}); + static getDerivedStateFromProps(nextProps, prevState) { + return {tags: nextProps.tags}; } handleShowTagManager() { @@ -2098,7 +2136,7 @@ class ContactsView extends React.Component { me.onSubsUpdated = undefined; } - componentWillReceiveProps(nextProps) { + UNSAFE_componentWillReceiveProps(nextProps) { if (this.props.topicSelected != nextProps.topicSelected) { // If topicSelecetd is changed externally, update the // topic with online status and access mode. @@ -2748,8 +2786,8 @@ class ValidationView extends React.PureComponent { this.handleSubmit = this.handleSubmit.bind(this); } - componentWillReceiveProps(nextProps) { - this.setState({code: nextProps.credCode}); + static getDerivedStateFromProps(nextProps, prevState) { + return {code: nextProps.credCode}; } handleChange(e) { @@ -2850,7 +2888,7 @@ class InfoView extends React.Component { } // No need to separately handle component mount. - componentWillReceiveProps(props) { + UNSAFE_componentWillReceiveProps(props) { var topic = Tinode.getTopic(props.topic); if (!topic) { return; @@ -3213,7 +3251,7 @@ class InfoView extends React.Component {
    + onChange={this.handleMuted} />