Add a placeholder to indicate a gap in the message history
The user can click the placeholder to fill in the gap.
This commit is contained in:
parent
14f0ed43c5
commit
dc711d494f
@ -26,6 +26,7 @@
|
|||||||
- Add support for rendering unfurls via [mod_ogp](https://modules.prosody.im/mod_ogp.html)
|
- Add support for rendering unfurls via [mod_ogp](https://modules.prosody.im/mod_ogp.html)
|
||||||
- Add a Description Of A Project (DOAP) file
|
- Add a Description Of A Project (DOAP) file
|
||||||
- Add ability to deregister nickname when closing a MUC by setting `auto_register_muc_nickname` to `'unregister'`.
|
- Add ability to deregister nickname when closing a MUC by setting `auto_register_muc_nickname` to `'unregister'`.
|
||||||
|
- Show a gap placeholder when there are gaps in the chat history. The user can click these to fill the gaps.
|
||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
|
2
Makefile
2
Makefile
@ -234,4 +234,4 @@ doc: node_modules docsdev apidoc
|
|||||||
|
|
||||||
PHONY: apidoc
|
PHONY: apidoc
|
||||||
apidoc:
|
apidoc:
|
||||||
$(JSDOC) --private --readme docs/source/jsdoc_intro.md -c docs/source/conf.json -d docs/html/api src/templates/**/*.js src/*.js src/**/*.js src/headless/**/*.js src/shared/**/*.js
|
$(JSDOC) --private --readme docs/source/jsdoc_intro.md -c docs/source/conf.json -d docs/html/api src/templates/*.js src/*.js src/**/*.js src/headless/**/*.js src/shared/**/*.js
|
||||||
|
@ -55,6 +55,7 @@ module.exports = function(config) {
|
|||||||
{ pattern: "src/plugins/controlbox/tests/login.js", type: 'module' },
|
{ pattern: "src/plugins/controlbox/tests/login.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/headlines-view/tests/headline.js", type: 'module' },
|
{ pattern: "src/plugins/headlines-view/tests/headline.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/mam-views/tests/mam.js", type: 'module' },
|
{ pattern: "src/plugins/mam-views/tests/mam.js", type: 'module' },
|
||||||
|
{ pattern: "src/plugins/mam-views/tests/placeholder.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/minimize/tests/minchats.js", type: 'module' },
|
{ pattern: "src/plugins/minimize/tests/minchats.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/muc-views/tests/autocomplete.js", type: 'module' },
|
{ pattern: "src/plugins/muc-views/tests/autocomplete.js", type: 'module' },
|
||||||
{ pattern: "src/plugins/muc-views/tests/component.js", type: 'module' },
|
{ pattern: "src/plugins/muc-views/tests/component.js", type: 'module' },
|
||||||
|
1
package-lock.json
generated
1
package-lock.json
generated
@ -2941,6 +2941,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@converse/skeletor": "github:conversejs/skeletor#f354bc530493a17d031f6f9c524cc34e073908e3",
|
"@converse/skeletor": "github:conversejs/skeletor#f354bc530493a17d031f6f9c524cc34e073908e3",
|
||||||
|
"dayjs": "1.10.4",
|
||||||
"filesize": "^6.1.0",
|
"filesize": "^6.1.0",
|
||||||
"localforage": "^1.9.0",
|
"localforage": "^1.9.0",
|
||||||
"localforage-driver-memory": "^1.0.5",
|
"localforage-driver-memory": "^1.0.5",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import ModelWithContact from './model-with-contact.js';
|
import ModelWithContact from './model-with-contact.js';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
import log from '../../log.js';
|
import log from '../../log.js';
|
||||||
import { _converse, api, converse } from '../../core.js';
|
import { _converse, api, converse } from '../../core.js';
|
||||||
import { getOpenPromise } from '@converse/openpromise';
|
import { getOpenPromise } from '@converse/openpromise';
|
||||||
@ -102,10 +103,52 @@ const MessageMixin = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a boolean indicating whether this message is ephemeral,
|
||||||
|
* meaning it will get automatically removed after ten seconds.
|
||||||
|
* @returns { boolean }
|
||||||
|
*/
|
||||||
isEphemeral () {
|
isEphemeral () {
|
||||||
return this.get('is_ephemeral');
|
return this.get('is_ephemeral');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a boolean indicating whether this message is a XEP-0245 /me command.
|
||||||
|
* @returns { boolean }
|
||||||
|
*/
|
||||||
|
isMeCommand () {
|
||||||
|
const text = this.getMessageText();
|
||||||
|
if (!text) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return text.startsWith('/me ');
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a boolean indicating whether this message is considered a followup
|
||||||
|
* message from the previous one. Followup messages are shown grouped together
|
||||||
|
* under one author heading.
|
||||||
|
* A message is considered a followup of it's predecessor when it's a chat
|
||||||
|
* message from the same author, within 10 minutes.
|
||||||
|
* @returns { boolean }
|
||||||
|
*/
|
||||||
|
isFollowup () {
|
||||||
|
const messages = this.collection.models;
|
||||||
|
const idx = messages.indexOf(this);
|
||||||
|
const prev_model = idx ? messages[idx-1] : null;
|
||||||
|
if (prev_model === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const date = dayjs(this.get('time'));
|
||||||
|
return this.get('from') === prev_model.get('from') &&
|
||||||
|
!this.isMeCommand() &&
|
||||||
|
!prev_model.isMeCommand() &&
|
||||||
|
this.get('type') !== 'info' &&
|
||||||
|
prev_model.get('type') !== 'info' &&
|
||||||
|
date.isBefore(dayjs(prev_model.get('time')).add(10, 'minutes')) &&
|
||||||
|
!!this.get('is_encrypted') === !!prev_model.get('is_encrypted');
|
||||||
|
},
|
||||||
|
|
||||||
getDisplayName () {
|
getDisplayName () {
|
||||||
if (this.get('type') === 'groupchat') {
|
if (this.get('type') === 'groupchat') {
|
||||||
return this.get('nick');
|
return this.get('nick');
|
||||||
@ -126,14 +169,6 @@ const MessageMixin = {
|
|||||||
return this.get('message');
|
return this.get('message');
|
||||||
},
|
},
|
||||||
|
|
||||||
isMeCommand () {
|
|
||||||
const text = this.getMessageText();
|
|
||||||
if (!text) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return text.startsWith('/me ');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send out an IQ stanza to request a file upload slot.
|
* Send out an IQ stanza to request a file upload slot.
|
||||||
* https://xmpp.org/extensions/xep-0363.html#request
|
* https://xmpp.org/extensions/xep-0363.html#request
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
* @copyright 2020, the Converse.js contributors
|
* @copyright 2020, the Converse.js contributors
|
||||||
* @license Mozilla Public License (MPLv2)
|
* @license Mozilla Public License (MPLv2)
|
||||||
*/
|
*/
|
||||||
import mam_api from './api.js';
|
|
||||||
import '../disco/index.js';
|
import '../disco/index.js';
|
||||||
|
import MAMPlaceholderMessage from './placeholder.js';
|
||||||
|
import mam_api from './api.js';
|
||||||
import {
|
import {
|
||||||
onMAMError,
|
onMAMError,
|
||||||
onMAMPreferences,
|
onMAMPreferences,
|
||||||
@ -31,7 +32,7 @@ converse.plugins.add('converse-mam', {
|
|||||||
|
|
||||||
Object.assign(api, mam_api);
|
Object.assign(api, mam_api);
|
||||||
// This is mainly done to aid with tests
|
// This is mainly done to aid with tests
|
||||||
Object.assign(_converse, { onMAMError, onMAMPreferences, handleMAMResult });
|
Object.assign(_converse, { onMAMError, onMAMPreferences, handleMAMResult, MAMPlaceholderMessage });
|
||||||
|
|
||||||
/************************ Event Handlers ************************/
|
/************************ Event Handlers ************************/
|
||||||
api.listen.on('addClientFeatures', () => api.disco.own.features.add(NS.MAM));
|
api.listen.on('addClientFeatures', () => api.disco.own.features.add(NS.MAM));
|
||||||
|
14
src/headless/plugins/mam/placeholder.js
Normal file
14
src/headless/plugins/mam/placeholder.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Model } from '@converse/skeletor/src/model.js';
|
||||||
|
import { converse } from '../../core.js';
|
||||||
|
|
||||||
|
const u = converse.env.utils;
|
||||||
|
|
||||||
|
export default class MAMPlaceholderMessage extends Model {
|
||||||
|
|
||||||
|
defaults () { // eslint-disable-line class-methods-use-this
|
||||||
|
return {
|
||||||
|
'msgid': u.getUniqueId(),
|
||||||
|
'is_ephemeral': false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
|
import MAMPlaceholderMessage from './placeholder.js';
|
||||||
import log from '@converse/headless/log';
|
import log from '@converse/headless/log';
|
||||||
import sizzle from 'sizzle';
|
import sizzle from 'sizzle';
|
||||||
|
import { _converse, api, converse } from '@converse/headless/core';
|
||||||
import { parseMUCMessage } from '@converse/headless/plugins/muc/parsers';
|
import { parseMUCMessage } from '@converse/headless/plugins/muc/parsers';
|
||||||
import { parseMessage } from '@converse/headless/plugins/chat/parsers';
|
import { parseMessage } from '@converse/headless/plugins/chat/parsers';
|
||||||
import { _converse, api, converse } from '@converse/headless/core';
|
|
||||||
|
|
||||||
const { Strophe, $iq } = converse.env;
|
const { Strophe, $iq } = converse.env;
|
||||||
const { NS } = Strophe;
|
const { NS } = Strophe;
|
||||||
@ -97,8 +98,8 @@ export async function handleMAMResult (model, result, query, options, should_pag
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch XEP-0313 archived messages based on the passed in criteria.
|
* @typedef { Object } MAMOptions
|
||||||
* @param { Object } options
|
* A map of MAM related options that may be passed to fetchArchivedMessages
|
||||||
* @param { integer } [options.max] - The maximum number of items to return.
|
* @param { integer } [options.max] - The maximum number of items to return.
|
||||||
* Defaults to "archived_messages_page_size"
|
* Defaults to "archived_messages_page_size"
|
||||||
* @param { string } [options.after] - The XEP-0359 stanza ID of a message
|
* @param { string } [options.after] - The XEP-0359 stanza ID of a message
|
||||||
@ -112,10 +113,17 @@ export async function handleMAMResult (model, result, query, options, should_pag
|
|||||||
* @param { string } [options.with] - The JID of the entity with
|
* @param { string } [options.with] - The JID of the entity with
|
||||||
* which messages were exchanged.
|
* which messages were exchanged.
|
||||||
* @param { boolean } [options.groupchat] - True if archive in groupchat.
|
* @param { boolean } [options.groupchat] - True if archive in groupchat.
|
||||||
* @param { ('forwards'|'backwards'|null)} [should_page=null] - Determines whether this function should
|
|
||||||
* recursively page through the entire result set if a limited number of results were returned.
|
|
||||||
*/
|
*/
|
||||||
export async function fetchArchivedMessages (model, options = {}, should_page=null) {
|
|
||||||
|
/**
|
||||||
|
* Fetch XEP-0313 archived messages based on the passed in criteria.
|
||||||
|
* @param { _converse.ChatBox | _converse.ChatRoom } model
|
||||||
|
* @param { MAMOptions } [options]
|
||||||
|
* @param { ('forwards'|'backwards'|null)} [should_page=null] - Determines whether
|
||||||
|
* this function should recursively page through the entire result set if a limited
|
||||||
|
* number of results were returned.
|
||||||
|
*/
|
||||||
|
export async function fetchArchivedMessages (model, options = {}, should_page = null) {
|
||||||
if (model.disable_mam) {
|
if (model.disable_mam) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -146,16 +154,49 @@ export async function fetchArchivedMessages (model, options = {}, should_page=nu
|
|||||||
}
|
}
|
||||||
return fetchArchivedMessages(model, options, should_page);
|
return fetchArchivedMessages(model, options, should_page);
|
||||||
} else {
|
} else {
|
||||||
// TODO: Add a special kind of message which will
|
createPlaceholder(model, options, result);
|
||||||
// render as a link to fetch further messages, either
|
|
||||||
// to fetch older messages or to fill in a gap.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a placeholder message which is used to indicate gaps in the history.
|
||||||
|
* @param { _converse.ChatBox | _converse.ChatRoom } model
|
||||||
|
* @param { MAMOptions } options
|
||||||
|
* @param { object } result - The RSM result object
|
||||||
|
*/
|
||||||
|
async function createPlaceholder (model, options, result) {
|
||||||
|
if (options.before == '' && (model.messages.length === 0 || !options.start)) {
|
||||||
|
// Fetching the latest MAM messages with an empty local cache
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.before && !options.start) {
|
||||||
|
// Infinite scrolling upward
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (options.before == null) { // eslint-disable-line no-eq-null
|
||||||
|
// Adding placeholders when paging forwards is not supported yet,
|
||||||
|
// since currently with standard Converse, we only page forwards
|
||||||
|
// when fetching the entire history (i.e. no gaps should arise).
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const msgs = await Promise.all(result.messages);
|
||||||
|
const { rsm } = result;
|
||||||
|
const key = `stanza_id ${model.get('jid')}`;
|
||||||
|
const adjacent_message = msgs.find(m => m[key] === rsm.result.first);
|
||||||
|
const msg_data = {
|
||||||
|
'template_hook': 'getMessageTemplate',
|
||||||
|
'time': new Date(new Date(adjacent_message['time']) - 1).toISOString(),
|
||||||
|
'before': rsm.result.first,
|
||||||
|
'start': options.start
|
||||||
|
}
|
||||||
|
model.messages.add(new MAMPlaceholderMessage(msg_data));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches messages that might have been archived *after*
|
* Fetches messages that might have been archived *after*
|
||||||
* the last archived message in our local cache.
|
* the last archived message in our local cache.
|
||||||
|
* @param { _converse.ChatBox | _converse.ChatRoom }
|
||||||
*/
|
*/
|
||||||
export function fetchNewestMessages (model) {
|
export function fetchNewestMessages (model) {
|
||||||
if (model.disable_mam) {
|
if (model.disable_mam) {
|
||||||
|
@ -386,7 +386,7 @@ describe("XEP-0363: HTTP File Upload", function () {
|
|||||||
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
|
const contact_jid = mock.cur_names[2].replace(/ /g,'.').toLowerCase() + '@montague.lit';
|
||||||
await mock.openChatBoxFor(_converse, contact_jid);
|
await mock.openChatBoxFor(_converse, contact_jid);
|
||||||
const view = _converse.chatboxviews.get(contact_jid);
|
const view = _converse.chatboxviews.get(contact_jid);
|
||||||
var file = {
|
const file = {
|
||||||
'type': 'image/jpeg',
|
'type': 'image/jpeg',
|
||||||
'size': '5242881',
|
'size': '5242881',
|
||||||
'lastModifiedDate': "",
|
'lastModifiedDate': "",
|
||||||
|
@ -741,7 +741,6 @@ describe("A Chat Message", function () {
|
|||||||
expect(u.hasClass('chat-msg--followup', view.querySelector(nth_child(2)))).toBe(false);
|
expect(u.hasClass('chat-msg--followup', view.querySelector(nth_child(2)))).toBe(false);
|
||||||
expect(view.querySelector(`${nth_child(2)} .chat-msg__text`).textContent).toBe("A message");
|
expect(view.querySelector(`${nth_child(2)} .chat-msg__text`).textContent).toBe("A message");
|
||||||
|
|
||||||
|
|
||||||
expect(u.hasClass('chat-msg--followup', view.querySelector(nth_child(3)))).toBe(true);
|
expect(u.hasClass('chat-msg--followup', view.querySelector(nth_child(3)))).toBe(true);
|
||||||
expect(view.querySelector(`${nth_child(3)} .chat-msg__text`).textContent).toBe(
|
expect(view.querySelector(`${nth_child(3)} .chat-msg__text`).textContent).toBe(
|
||||||
"Another message 3 minutes later");
|
"Another message 3 minutes later");
|
||||||
@ -1188,7 +1187,6 @@ describe("A Chat Message", function () {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
it("will cause the chat area to be scrolled down only if it was at the bottom originally",
|
it("will cause the chat area to be scrolled down only if it was at the bottom originally",
|
||||||
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
|
mock.initConverse(['chatBoxesFetched'], {}, async function (done, _converse) {
|
||||||
|
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
* @copyright 2021, the Converse.js contributors
|
* @copyright 2021, the Converse.js contributors
|
||||||
* @license Mozilla Public License (MPLv2)
|
* @license Mozilla Public License (MPLv2)
|
||||||
*/
|
*/
|
||||||
|
import './placeholder.js';
|
||||||
import { api, converse } from '@converse/headless/core';
|
import { api, converse } from '@converse/headless/core';
|
||||||
import { fetchMessagesOnScrollUp } from './utils.js';
|
import { fetchMessagesOnScrollUp, getPlaceholderTemplate } from './utils.js';
|
||||||
|
|
||||||
|
|
||||||
converse.plugins.add('converse-mam-views', {
|
converse.plugins.add('converse-mam-views', {
|
||||||
@ -12,5 +13,6 @@ converse.plugins.add('converse-mam-views', {
|
|||||||
|
|
||||||
initialize () {
|
initialize () {
|
||||||
api.listen.on('chatBoxScrolledUp', fetchMessagesOnScrollUp);
|
api.listen.on('chatBoxScrolledUp', fetchMessagesOnScrollUp);
|
||||||
|
api.listen.on('getMessageTemplate', getPlaceholderTemplate);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
33
src/plugins/mam-views/placeholder.js
Normal file
33
src/plugins/mam-views/placeholder.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { CustomElement } from 'shared/components/element.js';
|
||||||
|
import tpl_placeholder from './templates/placeholder.js';
|
||||||
|
import { api } from "@converse/headless/core";
|
||||||
|
import { fetchArchivedMessages } from '@converse/headless/plugins/mam/utils.js';
|
||||||
|
|
||||||
|
import './styles/placeholder.scss';
|
||||||
|
|
||||||
|
|
||||||
|
class Placeholder extends CustomElement {
|
||||||
|
|
||||||
|
static get properties () {
|
||||||
|
return {
|
||||||
|
'model': { type: Object }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
return tpl_placeholder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchMissingMessages (ev) {
|
||||||
|
ev?.preventDefault?.();
|
||||||
|
this.model.set('fetching', true);
|
||||||
|
const options = {
|
||||||
|
'before': this.model.get('before'),
|
||||||
|
'start': this.model.get('start')
|
||||||
|
}
|
||||||
|
await fetchArchivedMessages(this.model.collection.chatbox, options);
|
||||||
|
this.model.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api.elements.define('converse-mam-placeholder', Placeholder);
|
31
src/plugins/mam-views/styles/placeholder.scss
Normal file
31
src/plugins/mam-views/styles/placeholder.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
converse-mam-placeholder {
|
||||||
|
.mam-placeholder {
|
||||||
|
position: relative;
|
||||||
|
height: 2em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
&:before,
|
||||||
|
&:after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
&:before {
|
||||||
|
height: 1em;
|
||||||
|
top: 1em;
|
||||||
|
background: linear-gradient(-135deg, lightgray 0.5em, transparent 0) 0 0.5em, linear-gradient( 135deg, lightgray 0.5em, transparent 0) 0 0.5em;
|
||||||
|
background-position: top left;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
background-size: 1em 1em;
|
||||||
|
}
|
||||||
|
&:after {
|
||||||
|
height: 1em;
|
||||||
|
top: 0.75em;
|
||||||
|
background: linear-gradient(-135deg, var(--chat-background-color) 0.5em, transparent 0) 0 0.5em, linear-gradient( 135deg, var(--chat-background-color) 0.5em, transparent 0) 0 0.5em;
|
||||||
|
background-position: top left;
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
background-size: 1em 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
10
src/plugins/mam-views/templates/placeholder.js
Normal file
10
src/plugins/mam-views/templates/placeholder.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import tpl_spinner from 'templates/spinner.js';
|
||||||
|
import { __ } from 'i18n';
|
||||||
|
import { html } from 'lit-html';
|
||||||
|
|
||||||
|
export default (el) => {
|
||||||
|
return el.model.get('fetching') ? tpl_spinner({'classes': 'hor_centered'}) :
|
||||||
|
html`<a @click="${(ev) => el.fetchMissingMessages(ev)}" title="${__('Click to load missing messages')}">
|
||||||
|
<div class="message mam-placeholder"></div>
|
||||||
|
</a>`;
|
||||||
|
}
|
@ -18,7 +18,6 @@ describe("Message Archive Management", function () {
|
|||||||
|
|
||||||
describe("The XEP-0313 Archive", function () {
|
describe("The XEP-0313 Archive", function () {
|
||||||
|
|
||||||
|
|
||||||
it("is queried when the user scrolls up",
|
it("is queried when the user scrolls up",
|
||||||
mock.initConverse(['discoInitialized'], {'archived_messages_page_size': 2}, async function (done, _converse) {
|
mock.initConverse(['discoInitialized'], {'archived_messages_page_size': 2}, async function (done, _converse) {
|
||||||
|
|
||||||
@ -920,6 +919,7 @@ describe("Message Archive Management", function () {
|
|||||||
IQ_id = sendIQ.bind(this)(iq, callback, errback);
|
IQ_id = sendIQ.bind(this)(iq, callback, errback);
|
||||||
});
|
});
|
||||||
const promise = _converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'});
|
const promise = _converse.api.archive.query({'with': 'romeo@capulet.lit', 'max':'10'});
|
||||||
|
|
||||||
await u.waitUntil(() => sent_stanza);
|
await u.waitUntil(() => sent_stanza);
|
||||||
const queryid = sent_stanza.querySelector('query').getAttribute('queryid');
|
const queryid = sent_stanza.querySelector('query').getAttribute('queryid');
|
||||||
|
|
||||||
|
219
src/plugins/mam-views/tests/placeholder.js
Normal file
219
src/plugins/mam-views/tests/placeholder.js
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
/*global mock, converse */
|
||||||
|
|
||||||
|
const { Strophe, u } = converse.env;
|
||||||
|
|
||||||
|
describe("Message Archive Management", function () {
|
||||||
|
|
||||||
|
describe("A placeholder message", function () {
|
||||||
|
|
||||||
|
it("is created to indicate a gap in the history",
|
||||||
|
mock.initConverse(
|
||||||
|
['discoInitialized'],
|
||||||
|
{
|
||||||
|
'archived_messages_page_size': 2,
|
||||||
|
'persistent_store': 'localStorage',
|
||||||
|
'mam_request_all_pages': false
|
||||||
|
},
|
||||||
|
async function (done, _converse) {
|
||||||
|
|
||||||
|
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||||
|
const muc_jid = 'orchard@chat.shakespeare.lit';
|
||||||
|
const msgid = u.getUniqueId();
|
||||||
|
|
||||||
|
// We put an already cached message in localStorage
|
||||||
|
const key_prefix = `converse-test-persistent/${_converse.bare_jid}`;
|
||||||
|
let key = `${key_prefix}/converse.messages-${muc_jid}-${_converse.bare_jid}`;
|
||||||
|
localStorage.setItem(key, `["converse.messages-${muc_jid}-${_converse.bare_jid}-${msgid}"]`);
|
||||||
|
|
||||||
|
key = `${key_prefix}/converse.messages-${muc_jid}-${_converse.bare_jid}-${msgid}`;
|
||||||
|
const msgtxt = "existing cached message";
|
||||||
|
localStorage.setItem(key, `{
|
||||||
|
"body": "${msgtxt}",
|
||||||
|
"message": "${msgtxt}",
|
||||||
|
"editable":true,
|
||||||
|
"from": "${muc_jid}/romeo",
|
||||||
|
"fullname": "Romeo",
|
||||||
|
"id": "${msgid}",
|
||||||
|
"is_archived": false,
|
||||||
|
"is_only_emojis": false,
|
||||||
|
"nick": "jc",
|
||||||
|
"origin_id": "${msgid}",
|
||||||
|
"received": "2021-06-15T11:17:15.451Z",
|
||||||
|
"sender": "me",
|
||||||
|
"stanza_id ${muc_jid}": "1e1c2355-c5b8-4d48-9e33-1310724578c2",
|
||||||
|
"time": "2021-06-15T11:17:15.424Z",
|
||||||
|
"type": "groupchat",
|
||||||
|
"msgid": "${msgid}"
|
||||||
|
}`);
|
||||||
|
|
||||||
|
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
|
||||||
|
const view = _converse.chatboxviews.get(muc_jid);
|
||||||
|
|
||||||
|
let iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
|
||||||
|
const first_msg_id = _converse.connection.getUniqueId();
|
||||||
|
const second_msg_id = _converse.connection.getUniqueId();
|
||||||
|
const third_msg_id = _converse.connection.getUniqueId();
|
||||||
|
let message = u.toStanza(
|
||||||
|
`<message xmlns="jabber:client"
|
||||||
|
to="romeo@montague.lit/orchard"
|
||||||
|
from="${muc_jid}">
|
||||||
|
<result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${second_msg_id}">
|
||||||
|
<forwarded xmlns="urn:xmpp:forward:0">
|
||||||
|
<delay xmlns="urn:xmpp:delay" stamp="2021-06-15T11:18:23Z"/>
|
||||||
|
<message from="${muc_jid}/some1" type="groupchat">
|
||||||
|
<body>2nd MAM Message</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(message));
|
||||||
|
|
||||||
|
message = u.toStanza(
|
||||||
|
`<message xmlns="jabber:client"
|
||||||
|
to="romeo@montague.lit/orchard"
|
||||||
|
from="${muc_jid}">
|
||||||
|
<result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${third_msg_id}">
|
||||||
|
<forwarded xmlns="urn:xmpp:forward:0">
|
||||||
|
<delay xmlns="urn:xmpp:delay" stamp="2021-06-15T12:16:23Z"/>
|
||||||
|
<message from="${muc_jid}/some1" type="groupchat">
|
||||||
|
<body>3rd MAM Message</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(message));
|
||||||
|
|
||||||
|
// Clear so that we don't match the older query
|
||||||
|
while (sent_IQs.length) { sent_IQs.pop(); }
|
||||||
|
|
||||||
|
let result = u.toStanza(
|
||||||
|
`<iq type='result' id='${iq_get.getAttribute('id')}'>
|
||||||
|
<fin xmlns='urn:xmpp:mam:2'>
|
||||||
|
<set xmlns='http://jabber.org/protocol/rsm'>
|
||||||
|
<first index='0'>${second_msg_id}</first>
|
||||||
|
<last>${third_msg_id}</last>
|
||||||
|
<count>3</count>
|
||||||
|
</set>
|
||||||
|
</fin>
|
||||||
|
</iq>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(result));
|
||||||
|
await u.waitUntil(() => view.model.messages.length === 4);
|
||||||
|
|
||||||
|
const msg = view.model.messages.at(1);
|
||||||
|
expect(msg instanceof _converse.MAMPlaceholderMessage).toBe(true);
|
||||||
|
expect(msg.get('time')).toBe('2021-06-15T11:18:22.999Z');
|
||||||
|
|
||||||
|
const placeholder_el = view.querySelector('converse-mam-placeholder');
|
||||||
|
placeholder_el.firstElementChild.click();
|
||||||
|
await u.waitUntil(() => view.querySelector('converse-mam-placeholder .spinner'));
|
||||||
|
|
||||||
|
iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
|
||||||
|
expect(Strophe.serialize(iq_get)).toBe(
|
||||||
|
`<iq id="${iq_get.getAttribute('id')}" to="${muc_jid}" type="set" xmlns="jabber:client">`+
|
||||||
|
`<query queryid="${iq_get.querySelector('query').getAttribute('queryid')}" xmlns="urn:xmpp:mam:2">`+
|
||||||
|
`<x type="submit" xmlns="jabber:x:data">`+
|
||||||
|
`<field type="hidden" var="FORM_TYPE"><value>urn:xmpp:mam:2</value></field>`+
|
||||||
|
`<field var="start"><value>2021-06-15T11:17:15.424Z</value></field>`+
|
||||||
|
`</x>`+
|
||||||
|
`<set xmlns="http://jabber.org/protocol/rsm"><before>${view.model.messages.at(2).get(`stanza_id ${muc_jid}`)}</before>`+
|
||||||
|
`<max>2</max>`+
|
||||||
|
`</set>`+
|
||||||
|
`</query>`+
|
||||||
|
`</iq>`);
|
||||||
|
|
||||||
|
message = u.toStanza(
|
||||||
|
`<message xmlns="jabber:client"
|
||||||
|
to="romeo@montague.lit/orchard"
|
||||||
|
from="${muc_jid}">
|
||||||
|
<result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${first_msg_id}">
|
||||||
|
<forwarded xmlns="urn:xmpp:forward:0">
|
||||||
|
<delay xmlns="urn:xmpp:delay" stamp="2021-06-15T11:18:20Z"/>
|
||||||
|
<message from="${muc_jid}/some1" type="groupchat">
|
||||||
|
<body>1st MAM Message</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(message));
|
||||||
|
|
||||||
|
// Clear so that we don't match the older query
|
||||||
|
while (sent_IQs.length) { sent_IQs.pop(); }
|
||||||
|
|
||||||
|
result = u.toStanza(
|
||||||
|
`<iq type='result' id='${iq_get.getAttribute('id')}'>
|
||||||
|
<fin xmlns='urn:xmpp:mam:2' complete='true'>
|
||||||
|
<set xmlns='http://jabber.org/protocol/rsm'>
|
||||||
|
<first index='0'>${first_msg_id}</first>
|
||||||
|
<last>${first_msg_id}</last>
|
||||||
|
<count>1</count>
|
||||||
|
</set>
|
||||||
|
</fin>
|
||||||
|
</iq>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(result));
|
||||||
|
await u.waitUntil(() => view.model.messages.length === 4);
|
||||||
|
await u.waitUntil(() => view.querySelector('converse-mam-placeholder') === null);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("is not created when there isn't a gap because the cached history is empty",
|
||||||
|
mock.initConverse(['discoInitialized'], {'archived_messages_page_size': 2},
|
||||||
|
async function (done, _converse) {
|
||||||
|
|
||||||
|
const sent_IQs = _converse.connection.IQ_stanzas;
|
||||||
|
const muc_jid = 'orchard@chat.shakespeare.lit';
|
||||||
|
await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
|
||||||
|
const view = _converse.chatboxviews.get(muc_jid);
|
||||||
|
const iq_get = await u.waitUntil(() => sent_IQs.filter(iq => iq.querySelector(`iq query[xmlns="${Strophe.NS.MAM}"]`)).pop());
|
||||||
|
|
||||||
|
const first_msg_id = _converse.connection.getUniqueId();
|
||||||
|
const last_msg_id = _converse.connection.getUniqueId();
|
||||||
|
let message = u.toStanza(
|
||||||
|
`<message xmlns="jabber:client"
|
||||||
|
to="romeo@montague.lit/orchard"
|
||||||
|
from="${muc_jid}">
|
||||||
|
<result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${first_msg_id}">
|
||||||
|
<forwarded xmlns="urn:xmpp:forward:0">
|
||||||
|
<delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:15:23Z"/>
|
||||||
|
<message from="${muc_jid}/some1" type="groupchat">
|
||||||
|
<body>2nd Message</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(message));
|
||||||
|
|
||||||
|
message = u.toStanza(
|
||||||
|
`<message xmlns="jabber:client"
|
||||||
|
to="romeo@montague.lit/orchard"
|
||||||
|
from="${muc_jid}">
|
||||||
|
<result xmlns="urn:xmpp:mam:2" queryid="${iq_get.querySelector('query').getAttribute('queryid')}" id="${last_msg_id}">
|
||||||
|
<forwarded xmlns="urn:xmpp:forward:0">
|
||||||
|
<delay xmlns="urn:xmpp:delay" stamp="2018-01-09T06:16:23Z"/>
|
||||||
|
<message from="${muc_jid}/some1" type="groupchat">
|
||||||
|
<body>3rd Message</body>
|
||||||
|
</message>
|
||||||
|
</forwarded>
|
||||||
|
</result>
|
||||||
|
</message>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(message));
|
||||||
|
|
||||||
|
// Clear so that we don't match the older query
|
||||||
|
while (sent_IQs.length) { sent_IQs.pop(); }
|
||||||
|
|
||||||
|
const result = u.toStanza(
|
||||||
|
`<iq type='result' id='${iq_get.getAttribute('id')}'>
|
||||||
|
<fin xmlns='urn:xmpp:mam:2'>
|
||||||
|
<set xmlns='http://jabber.org/protocol/rsm'>
|
||||||
|
<first index='0'>${first_msg_id}</first>
|
||||||
|
<last>${last_msg_id}</last>
|
||||||
|
<count>3</count>
|
||||||
|
</set>
|
||||||
|
</fin>
|
||||||
|
</iq>`);
|
||||||
|
_converse.connection._dataRecv(mock.createRequest(result));
|
||||||
|
await u.waitUntil(() => view.model.messages.length === 2);
|
||||||
|
expect(true).toBe(true);
|
||||||
|
done();
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
@ -1,5 +1,16 @@
|
|||||||
import { fetchArchivedMessages } from '@converse/headless/plugins/mam/utils';
|
import MAMPlaceholderMessage from '@converse/headless/plugins/mam/placeholder.js';
|
||||||
import { _converse, api } from '@converse/headless/core';
|
import { _converse, api } from '@converse/headless/core';
|
||||||
|
import { fetchArchivedMessages } from '@converse/headless/plugins/mam/utils';
|
||||||
|
import { html } from 'lit-html';
|
||||||
|
|
||||||
|
|
||||||
|
export function getPlaceholderTemplate (message, tpl) {
|
||||||
|
if (message instanceof MAMPlaceholderMessage) {
|
||||||
|
return html`<converse-mam-placeholder .model=${message}></converse-mam-placeholder>`;
|
||||||
|
} else {
|
||||||
|
return tpl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function fetchMessagesOnScrollUp (view) {
|
export async function fetchMessagesOnScrollUp (view) {
|
||||||
if (view.model.messages.length) {
|
if (view.model.messages.length) {
|
||||||
@ -17,7 +28,6 @@ export async function fetchMessagesOnScrollUp (view) {
|
|||||||
if (api.settings.get('allow_url_history_change')) {
|
if (api.settings.get('allow_url_history_change')) {
|
||||||
_converse.router.history.navigate(`#${oldest_message.get('msgid')}`);
|
_converse.router.history.navigate(`#${oldest_message.get('msgid')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => view.model.ui.set('chat-content-spinner-top', false), 250);
|
setTimeout(() => view.model.ui.set('chat-content-spinner-top', false), 250);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { api } from "@converse/headless/core";
|
|||||||
import { getDayIndicator } from './utils.js';
|
import { getDayIndicator } from './utils.js';
|
||||||
import { html } from 'lit';
|
import { html } from 'lit';
|
||||||
import { repeat } from 'lit/directives/repeat.js';
|
import { repeat } from 'lit/directives/repeat.js';
|
||||||
|
import { until } from 'lit/directives/until.js';
|
||||||
|
|
||||||
|
|
||||||
export default class MessageHistory extends CustomElement {
|
export default class MessageHistory extends CustomElement {
|
||||||
@ -17,20 +18,28 @@ export default class MessageHistory extends CustomElement {
|
|||||||
|
|
||||||
render () {
|
render () {
|
||||||
const msgs = this.messages;
|
const msgs = this.messages;
|
||||||
return msgs.length ? html`${repeat(msgs, m => m.get('id'), m => this.renderMessage(m)) }` : '';
|
if (msgs.length) {
|
||||||
|
return repeat(msgs, m => m.get('id'), m => html`${this.renderMessage(m)}`)
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderMessage (model) {
|
renderMessage (model) {
|
||||||
if (model.get('dangling_retraction') || model.get('is_only_key')) {
|
if (model.get('dangling_retraction') || model.get('is_only_key')) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const day = getDayIndicator(model);
|
const template_hook = model.get('template_hook')
|
||||||
const templates = day ? [day] : [];
|
if (typeof template_hook === 'string') {
|
||||||
const message = html`<converse-chat-message
|
const template_promise = api.hook(template_hook, model, '');
|
||||||
jid="${this.model.get('jid')}"
|
return until(template_promise, '');
|
||||||
mid="${model.get('id')}"></converse-chat-message>`
|
} else {
|
||||||
|
const template = html`<converse-chat-message
|
||||||
return [...templates, message];
|
jid="${this.model.get('jid')}"
|
||||||
|
mid="${model.get('id')}"></converse-chat-message>`
|
||||||
|
const day = getDayIndicator(model);
|
||||||
|
return day ? [day, template] : template;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import 'shared/registry';
|
|||||||
import MessageVersionsModal from 'modals/message-versions.js';
|
import MessageVersionsModal from 'modals/message-versions.js';
|
||||||
import OccupantModal from 'modals/occupant.js';
|
import OccupantModal from 'modals/occupant.js';
|
||||||
import UserDetailsModal from 'modals/user-details.js';
|
import UserDetailsModal from 'modals/user-details.js';
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import filesize from 'filesize';
|
import filesize from 'filesize';
|
||||||
import tpl_message from './templates/message.js';
|
import tpl_message from './templates/message.js';
|
||||||
import tpl_spinner from 'templates/spinner.js';
|
import tpl_spinner from 'templates/spinner.js';
|
||||||
@ -16,7 +15,7 @@ import { getHats } from './utils.js';
|
|||||||
import { html } from 'lit';
|
import { html } from 'lit';
|
||||||
import { renderAvatar } from 'shared/directives/avatar';
|
import { renderAvatar } from 'shared/directives/avatar';
|
||||||
|
|
||||||
const { Strophe } = converse.env;
|
const { Strophe, dayjs } = converse.env;
|
||||||
const u = converse.env.utils;
|
const u = converse.env.utils;
|
||||||
|
|
||||||
|
|
||||||
@ -137,23 +136,6 @@ export default class Message extends CustomElement {
|
|||||||
this.parentElement.removeChild(this);
|
this.parentElement.removeChild(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
isFollowup () {
|
|
||||||
const messages = this.model.collection.models;
|
|
||||||
const idx = messages.indexOf(this.model);
|
|
||||||
const prev_model = idx ? messages[idx-1] : null;
|
|
||||||
if (prev_model === null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const date = dayjs(this.model.get('time'));
|
|
||||||
return this.model.get('from') === prev_model.get('from') &&
|
|
||||||
!this.model.isMeCommand() &&
|
|
||||||
!prev_model.isMeCommand() &&
|
|
||||||
this.model.get('type') !== 'info' &&
|
|
||||||
prev_model.get('type') !== 'info' &&
|
|
||||||
date.isBefore(dayjs(prev_model.get('time')).add(10, 'minutes')) &&
|
|
||||||
!!this.model.get('is_encrypted') === !!prev_model.get('is_encrypted');
|
|
||||||
}
|
|
||||||
|
|
||||||
isRetracted () {
|
isRetracted () {
|
||||||
return this.model.get('retracted') || this.model.get('moderated') === 'retracted';
|
return this.model.get('retracted') || this.model.get('moderated') === 'retracted';
|
||||||
}
|
}
|
||||||
@ -173,7 +155,7 @@ export default class Message extends CustomElement {
|
|||||||
|
|
||||||
getExtraMessageClasses () {
|
getExtraMessageClasses () {
|
||||||
const extra_classes = [
|
const extra_classes = [
|
||||||
this.isFollowup() ? 'chat-msg--followup' : null,
|
this.model.isFollowup() ? 'chat-msg--followup' : null,
|
||||||
this.model.get('is_delayed') ? 'delayed' : null,
|
this.model.get('is_delayed') ? 'delayed' : null,
|
||||||
this.model.isMeCommand() ? 'chat-msg--action' : null,
|
this.model.isMeCommand() ? 'chat-msg--action' : null,
|
||||||
this.isRetracted() ? 'chat-msg--retracted' : null,
|
this.isRetracted() ? 'chat-msg--retracted' : null,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { _converse, api } from '@converse/headless/core';
|
|
||||||
import dayjs from 'dayjs';
|
|
||||||
import tpl_new_day from "./templates/new-day.js";
|
import tpl_new_day from "./templates/new-day.js";
|
||||||
|
import { _converse, api, converse } from '@converse/headless/core';
|
||||||
|
|
||||||
|
const { dayjs } = converse.env;
|
||||||
|
|
||||||
export function onScrolledDown (model) {
|
export function onScrolledDown (model) {
|
||||||
if (!model.isHidden()) {
|
if (!model.isHidden()) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* @module icons.js
|
|
||||||
* @copyright Alfredo Medrano Sánchez and the Converse.js contributors
|
* @copyright Alfredo Medrano Sánchez and the Converse.js contributors
|
||||||
* @description
|
* @description
|
||||||
* Component inspired by the one from fa-icons
|
* Component inspired by the one from fa-icons
|
||||||
|
@ -366,6 +366,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spinner__container {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
.spinner {
|
.spinner {
|
||||||
animation: spin 2s infinite, linear;
|
animation: spin 2s infinite, linear;
|
||||||
width: 1em;
|
width: 1em;
|
||||||
@ -386,9 +389,8 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
.hor_centered {
|
.hor_centered {
|
||||||
width: 100%;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
display: block;
|
display: block !important;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
clear: both;
|
clear: both;
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
import { html } from "lit";
|
import { html } from "lit";
|
||||||
|
|
||||||
export default (o={}) => html`<span class="spinner fa fa-spinner centered ${o.classes || ''}"/>`
|
export default (o={}) => {
|
||||||
|
if (o.classes?.includes('hor_centered')) {
|
||||||
|
return html`<div class="spinner__container"><span class="spinner fa fa-spinner centered ${o.classes || ''}"/></div>`
|
||||||
|
} else {
|
||||||
|
return html`<span class="spinner fa fa-spinner centered ${o.classes || ''}"/>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
modtools_disable_query: ['moderator', 'participant', 'visitor'],
|
modtools_disable_query: ['moderator', 'participant', 'visitor'],
|
||||||
enable_smacks: true,
|
enable_smacks: true,
|
||||||
// connection_options: { 'worker': '/dist/shared-connection-worker.js' },
|
// connection_options: { 'worker': '/dist/shared-connection-worker.js' },
|
||||||
persistent_store: 'IndexedDB',
|
// persistent_store: 'IndexedDB',
|
||||||
message_archiving: 'always',
|
message_archiving: 'always',
|
||||||
muc_domain: 'conference.chat.example.org',
|
muc_domain: 'conference.chat.example.org',
|
||||||
muc_respect_autojoin: true,
|
muc_respect_autojoin: true,
|
||||||
|
Loading…
Reference in New Issue
Block a user