Skip to content

Commit

Permalink
More work on cleaning up src/headless/utils/index.js
Browse files Browse the repository at this point in the history
  • Loading branch information
jcbrand committed Sep 28, 2023
1 parent 3f0a819 commit 45a1890
Show file tree
Hide file tree
Showing 11 changed files with 123 additions and 113 deletions.
3 changes: 2 additions & 1 deletion src/headless/plugins/chat/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { isEmptyMessage } from '../../utils/index.js';
import { isUniView } from '../../utils/session.js';
import { parseMessage } from './parsers.js';
import { sendMarker } from '../../shared/actions.js';
import { isNewMessage } from './utils.js';

const { Strophe, $msg } = converse.env;

Expand Down Expand Up @@ -1094,7 +1095,7 @@ const ChatBox = ModelWithContact.extend({
if (!message?.get('body')) {
return
}
if (u.isNewMessage(message)) {
if (isNewMessage(message)) {
if (message.get('sender') === 'me') {
// We remove the "scrolled" flag so that the chat area
// gets scrolled down. We always want to scroll down
Expand Down
18 changes: 18 additions & 0 deletions src/headless/plugins/chat/utils.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import sizzle from "sizzle";
import { Model } from '@converse/skeletor/src/model.js';
import _converse from '../../shared/_converse.js';
import api, { converse } from '../../shared/api/index.js';
import log from '../../log.js';
Expand All @@ -24,6 +26,22 @@ export async function onClearSession () {
}
}

export function isNewMessage (message) {
/* Given a stanza, determine whether it's a new
* message, i.e. not a MAM archived one.
*/
if (message instanceof Element) {
return !(
sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, message).length &&
sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, message).length
);
} else if (message instanceof Model) {
message = message.attributes;
}
return !(message['is_delayed'] && message['is_archived']);
}


async function handleErrorMessage (stanza) {
const from_jid = Strophe.getBareJidFromJid(stanza.getAttribute('from'));
if (u.isSameBareJID(from_jid, _converse.bare_jid)) {
Expand Down
3 changes: 2 additions & 1 deletion src/headless/plugins/muc/muc.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { getUniqueId, safeSave } from '../../utils/index.js';
import { isUniView } from '../../utils/session.js';
import { parseMUCMessage, parseMUCPresence } from './parsers.js';
import { sendMarker } from '../../shared/actions.js';
import { shouldCreateGroupchatMessage } from './utils.js';

const { u } = converse.env;

Expand Down Expand Up @@ -2296,7 +2297,7 @@ const ChatRoomMixin = {
if (attrs['chat_state']) {
this.updateNotifications(attrs.nick, attrs.chat_state);
}
if (u.shouldCreateGroupchatMessage(attrs)) {
if (shouldCreateGroupchatMessage(attrs)) {
const msg = await handleCorrection(this, attrs) || (await this.createMessage(attrs));
this.removeNotification(attrs.nick, ['composing', 'paused']);
this.handleUnreadMessage(msg);
Expand Down
9 changes: 9 additions & 0 deletions src/headless/plugins/muc/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ import { safeSave } from '../../utils/index.js';

const { Strophe, sizzle, u } = converse.env;

export function isChatRoom (model) {
return model?.get('type') === 'chatroom';
}

export function shouldCreateGroupchatMessage (attrs) {
return attrs.nick && (u.shouldCreateMessage(attrs) || attrs.is_tombstone);
}


export function getAutoFetchedAffiliationLists () {
const affs = api.settings.get('muc_fetch_members');
return Array.isArray(affs) ? affs : affs ? ['member', 'admin', 'owner'] : [];
Expand Down
2 changes: 1 addition & 1 deletion src/headless/shared/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import log from '../log.js';
import sizzle from 'sizzle';
import { Strophe } from 'strophe.js';
import { URL_PARSE_OPTIONS } from './constants.js';
import { decodeHTMLEntities } from '../utils/index.js';
import { decodeHTMLEntities } from '../utils/html.js';
import { rejectMessage } from './actions';
import {
isAudioURL,
Expand Down
26 changes: 26 additions & 0 deletions src/headless/utils/html.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import DOMPurify from 'dompurify';
import { Strophe } from 'strophe.js';

/**
Expand Down Expand Up @@ -62,3 +63,28 @@ export function stringToElement (s) {
export function queryChildren (el, selector) {
return Array.from(el.childNodes).filter(el => (el instanceof Element) && el.matches(selector));
}

/**
* @param {Element} el - the DOM element
* @return {number}
*/
export function siblingIndex (el) {
/* eslint-disable no-cond-assign */
for (var i = 0; el = el.previousElementSibling; i++);
return i;
}

const element = document.createElement('div');

/**
* @param {string} str
* @return {string}
*/
export function decodeHTMLEntities (str) {
if (str && typeof str === 'string') {
element.innerHTML = DOMPurify.sanitize(str);
str = element.textContent;
element.textContent = '';
}
return str;
}
138 changes: 34 additions & 104 deletions src/headless/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
* @license Mozilla Public License (MPLv2)
* @description This is the core utilities module.
*/
import DOMPurify from 'dompurify';
import sizzle from "sizzle";
import { Model } from '@converse/skeletor/src/model.js';
import { Strophe, toStanza } from 'strophe.js';
import { toStanza } from 'strophe.js';
import { getOpenPromise } from '@converse/openpromise';
import { saveWindowState, shouldClearCache } from './session.js';
import { merge, isError, isFunction } from './object.js';
import { createStore, getDefaultStore } from './storage.js';
import { waitUntil } from './promise.js';
import { isValidJID, isValidMUCJID, isSameBareJID } from './jid.js';
import { isErrorStanza } from './stanza.js';
import {
getCurrentWord,
getSelectValues,
Expand Down Expand Up @@ -52,26 +51,7 @@ import {
* The utils object
* @namespace u
*/
const u = {
arrayBufferToBase64,
arrayBufferToHex,
arrayBufferToString,
base64ToArrayBuffer,
checkFileTypes,
getSelectValues,
getURI,
isAllowedProtocolForMedia,
isAudioURL,
isError,
isFunction,
isGIFURL,
isImageURL,
isURLWithImageExtension,
isVideoURL,
shouldRenderMediaFromURL,
stringToArrayBuffer,
webForm2xForm,
};
const u = {};


export function isEmptyMessage (attrs) {
Expand Down Expand Up @@ -99,7 +79,7 @@ export function prefixMentions (message) {
return text;
}

u.getLongestSubstring = function (string, candidates) {
function getLongestSubstring (string, candidates) {
function reducer (accumulator, current_value) {
if (string.startsWith(current_value)) {
if (current_value.length > accumulator.length) {
Expand All @@ -114,80 +94,23 @@ u.getLongestSubstring = function (string, candidates) {
return candidates.reduce(reducer, '');
}

u.isNewMessage = function (message) {
/* Given a stanza, determine whether it's a new
* message, i.e. not a MAM archived one.
*/
if (message instanceof Element) {
return !(
sizzle(`result[xmlns="${Strophe.NS.MAM}"]`, message).length &&
sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, message).length
);
} else if (message instanceof Model) {
message = message.attributes;
}
return !(message['is_delayed'] && message['is_archived']);
};

u.shouldCreateMessage = function (attrs) {
function shouldCreateMessage (attrs) {
return attrs['retracted'] || // Retraction received *before* the message
!isEmptyMessage(attrs);
}

u.shouldCreateGroupchatMessage = function (attrs) {
return attrs.nick && (u.shouldCreateMessage(attrs) || attrs.is_tombstone);
}

u.isChatRoom = function (model) {
return model && (model.get('type') === 'chatroom');
}

export function isErrorObject (o) {
return o instanceof Error;
}

u.isErrorStanza = function (stanza) {
if (!isElement(stanza)) {
return false;
}
return stanza.getAttribute('type') === 'error';
}

u.isForbiddenError = function (stanza) {
if (!isElement(stanza)) {
return false;
}
return sizzle(`error[type="auth"] forbidden[xmlns="${Strophe.NS.STANZAS}"]`, stanza).length > 0;
}

u.isServiceUnavailableError = function (stanza) {
if (!isElement(stanza)) {
return false;
}
return sizzle(`error[type="cancel"] service-unavailable[xmlns="${Strophe.NS.STANZAS}"]`, stanza).length > 0;
}

u.getAttribute = function (key, item) {
return item.get(key);
};

u.isPersistableModel = function (model) {
return model.collection && model.collection.browserStorage;
};

u.getResolveablePromise = getOpenPromise;
u.getOpenPromise = getOpenPromise;

/**
* Call the callback once all the events have been triggered
* @private
* @method u#onMultipleEvents
* @param { Array } events: An array of objects, with keys `object` and
* `event`, representing the event name and the object it's triggered upon.
* @param { Function } callback: The function to call once all events have
* been triggered.
*/
u.onMultipleEvents = function (events=[], callback) {
function onMultipleEvents (events=[], callback) {
let triggered = [];

function handler (result) {
Expand All @@ -198,24 +121,20 @@ u.onMultipleEvents = function (events=[], callback) {
}
}
events.forEach(e => e.object.on(e.event, handler));
};
}

function isPersistableModel (model) {
return model.collection && model.collection.browserStorage;
}

export function safeSave (model, attributes, options) {
if (u.isPersistableModel(model)) {
if (isPersistableModel(model)) {
model.save(attributes, options);
} else {
model.set(attributes, options);
}
}


u.siblingIndex = function (el) {
/* eslint-disable no-cond-assign */
for (var i = 0; el = el.previousElementSibling; i++);
return i;
};

/**
* @param {Element} el
* @param {string} name
Expand Down Expand Up @@ -251,43 +170,54 @@ export function getUniqueId (suffix) {
}
}


const element = document.createElement('div');

export function decodeHTMLEntities (str) {
if (str && typeof str === 'string') {
element.innerHTML = DOMPurify.sanitize(str);
str = element.textContent;
element.textContent = '';
}
return str;
}

export default Object.assign({
arrayBufferToBase64,
arrayBufferToHex,
arrayBufferToString,
base64ToArrayBuffer,
checkFileTypes,
createStore,
getCurrentWord,
getDefaultStore,
getLongestSubstring,
getOpenPromise,
getOuterWidth,
getRandomInt,
getSelectValues,
getURI,
getUniqueId,
isAllowedProtocolForMedia,
isAudioURL,
isElement,
isEmptyMessage,
isError,
isErrorObject,
isErrorStanza,
isFunction,
isGIFURL,
isImageURL,
isMentionBoundary,
isSameBareJID,
isTagEqual,
isURLWithImageExtension,
isValidJID,
isValidMUCJID,
isVideoURL,
merge,
onMultipleEvents,
placeCaretAtEnd,
prefixMentions,
queryChildren,
replaceCurrentWord,
safeSave,
saveWindowState,
shouldClearCache,
shouldCreateMessage,
shouldRenderMediaFromURL,
stringToArrayBuffer,
stringToElement,
toStanza,
triggerEvent,
webForm2xForm,
waitUntil, // TODO: remove. Only the API should be used
}, u);
24 changes: 24 additions & 0 deletions src/headless/utils/stanza.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import sizzle from "sizzle";
import { Strophe } from 'strophe.js';
import { isElement } from './html.js';

export function isErrorStanza (stanza) {
if (!isElement(stanza)) {
return false;
}
return stanza.getAttribute('type') === 'error';
}

export function isForbiddenError (stanza) {
if (!isElement(stanza)) {
return false;
}
return sizzle(`error[type="auth"] forbidden[xmlns="${Strophe.NS.STANZAS}"]`, stanza).length > 0;
}

export function isServiceUnavailableError (stanza) {
if (!isElement(stanza)) {
return false;
}
return sizzle(`error[type="cancel"] service-unavailable[xmlns="${Strophe.NS.STANZAS}"]`, stanza).length > 0;
}
1 change: 0 additions & 1 deletion src/plugins/adhoc-views/tests/adhoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@ describe("Ad-hoc commands consisting of multiple steps", function () {
`));

let button = await u.waitUntil(() => modal.querySelector('input[data-action="next"]'));
debugger;
button.click();

sel = `iq[to="${entity_jid}"] command[sessionid="${sessionid}"]`;
Expand Down
Loading

0 comments on commit 45a1890

Please sign in to comment.