Bladeren bron

Move files around to try and create some consistency and order

JC Brand 4 jaren geleden
bovenliggende
commit
f897596215
58 gewijzigde bestanden met toevoegingen van 417 en 417 verwijderingen
  1. 1 1
      src/converse.js
  2. 1 1
      src/modals/templates/occupant.js
  3. 1 1
      src/modals/templates/profile.js
  4. 4 4
      src/modals/templates/user-details.js
  5. 1 1
      src/plugins/bookmark-views/form.js
  6. 1 1
      src/plugins/chatview/templates/chat-head.js
  7. 1 1
      src/plugins/controlbox/index.js
  8. 1 1
      src/plugins/controlbox/navback.js
  9. 1 1
      src/plugins/controlbox/templates/loginpanel.js
  10. 1 1
      src/plugins/controlbox/toggle.js
  11. 1 1
      src/plugins/dragresize/components/dragresize.js
  12. 1 1
      src/plugins/minimize/components/minimized-chat.js
  13. 1 1
      src/plugins/muc-views/adhoc-commands.js
  14. 1 1
      src/plugins/muc-views/chatarea.js
  15. 1 1
      src/plugins/muc-views/config-form.js
  16. 1 1
      src/plugins/muc-views/destroyed.js
  17. 1 1
      src/plugins/muc-views/disconnected.js
  18. 1 1
      src/plugins/muc-views/nickname-form.js
  19. 1 1
      src/plugins/muc-views/password-form.js
  20. 1 1
      src/plugins/muc-views/sidebar.js
  21. 2 2
      src/plugins/muc-views/templates/muc-head.js
  22. 3 3
      src/plugins/profile/statusview.js
  23. 1 1
      src/plugins/rootview/root.js
  24. 1 1
      src/plugins/rootview/templates/root.js
  25. 1 1
      src/plugins/rosterview/contactview.js
  26. 1 1
      src/plugins/rosterview/templates/roster_item.js
  27. 1 1
      src/shared/autocomplete/component.js
  28. 2 2
      src/shared/avatar.js
  29. 1 1
      src/shared/chat/chat-content.js
  30. 1 1
      src/shared/chat/emoji-picker-content.js
  31. 2 2
      src/shared/chat/emoji-picker.js
  32. 2 2
      src/shared/chat/help-messages.js
  33. 1 1
      src/shared/chat/message-actions.js
  34. 1 1
      src/shared/chat/message-body.js
  35. 1 1
      src/shared/chat/message-history.js
  36. 4 4
      src/shared/chat/message.js
  37. 1 1
      src/shared/chat/templates/message.js
  38. 1 1
      src/shared/chat/toolbar.js
  39. 1 1
      src/shared/chat/unfurl.js
  40. 0 0
      src/shared/components/brand-byline.js
  41. 0 0
      src/shared/components/brand-heading.js
  42. 0 0
      src/shared/components/brand-logo.js
  43. 1 1
      src/shared/components/dropdown.js
  44. 0 0
      src/shared/components/element.js
  45. 0 0
      src/shared/components/font-awesome.js
  46. 0 0
      src/shared/components/icons.js
  47. 2 2
      src/shared/components/image-picker.js
  48. 43 0
      src/shared/components/rich-text.js
  49. 1 1
      src/shared/directives/avatar.js
  50. 0 0
      src/shared/directives/image.js
  51. 0 0
      src/shared/directives/retraction.js
  52. 1 1
      src/shared/directives/rich-text.js
  53. 1 1
      src/shared/directives/styling.js
  54. 0 323
      src/shared/message/text.js
  55. 315 35
      src/shared/rich-text.js
  56. 1 1
      src/shared/styling.js
  57. 0 0
      src/shared/templates/avatar.js
  58. 1 1
      src/templates/image.js

+ 1 - 1
src/converse.js

@@ -36,7 +36,7 @@ import "./plugins/singleton.js";
 /* END: Removable components */
 
 import { _converse, converse } from "@converse/headless/core";
-import { CustomElement } from 'components/element';
+import { CustomElement } from 'shared/components/element';
 
 _converse.CustomElement = CustomElement;
 

+ 1 - 1
src/modals/templates/occupant.js

@@ -1,6 +1,6 @@
 import { html } from "lit-html";
 import { modal_close_button, modal_header_close_button } from "./buttons.js"
-import { renderAvatar } from '../../templates/directives/avatar';
+import { renderAvatar } from 'shared/directives/avatar';
 
 
 export default (o) => {

+ 1 - 1
src/modals/templates/profile.js

@@ -1,4 +1,4 @@
-import "components/image-picker.js";
+import "shared/components/image-picker.js";
 import spinner from "templates/spinner.js";
 import { __ } from 'i18n';
 import { _converse, converse } from  "@converse/headless/core";

+ 4 - 4
src/modals/templates/user-details.js

@@ -1,7 +1,7 @@
-import { __ } from '../../i18n';
-import { html } from "lit-html";
-import avatar from "../../templates/avatar.js";
-import { modal_close_button, modal_header_close_button } from "./buttons.js"
+import avatar from 'shared/templates/avatar.js';
+import { __ } from 'i18n';
+import { html } from 'lit-html';
+import { modal_close_button, modal_header_close_button } from './buttons.js'
 
 
 const device_fingerprint = (o) => {

+ 1 - 1
src/plugins/bookmark-views/form.js

@@ -1,5 +1,5 @@
 import tpl_muc_bookmark_form from './templates/form.js';
-import { CustomElement } from 'components/element';
+import { CustomElement } from 'shared/components/element';
 import { _converse, api } from "@converse/headless/core";
 
 

+ 1 - 1
src/plugins/chatview/templates/chat-head.js

@@ -1,6 +1,6 @@
 import { _converse } from '@converse/headless/core';
 import { html } from "lit-html";
-import { renderAvatar } from 'templates/directives/avatar.js';
+import { renderAvatar } from 'shared/directives/avatar.js';
 import { until } from 'lit-html/directives/until.js';
 
 

+ 1 - 1
src/plugins/controlbox/index.js

@@ -3,7 +3,7 @@
  * @copyright 2020, the Converse.js contributors
  * @license Mozilla Public License (MPLv2)
  */
-import "../../components/brand-heading";
+import "shared/components/brand-heading";
 import "../chatview/index.js";
 import './loginpanel.js';
 import './navback.js';

+ 1 - 1
src/plugins/controlbox/navback.js

@@ -1,5 +1,5 @@
 import tpl_controlbox_navback from "./templates/navback.js";
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { api } from "@converse/headless/core";
 
 

+ 1 - 1
src/plugins/controlbox/templates/loginpanel.js

@@ -1,4 +1,4 @@
-import 'components/brand-heading.js';
+import 'shared/components/brand-heading.js';
 import tpl_spinner from 'templates/spinner.js';
 import { __ } from 'i18n';
 import { _converse, api } from "@converse/headless/core";

+ 1 - 1
src/plugins/controlbox/toggle.js

@@ -1,5 +1,5 @@
 import tpl_controlbox_toggle from "./templates/toggle.js";
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { _converse, api } from "@converse/headless/core";
 import { showControlBox } from './utils.js';
 

+ 1 - 1
src/plugins/dragresize/components/dragresize.js

@@ -1,5 +1,5 @@
 import tpl_dragresize from "../templates/dragresize.js";
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 
 
 class ConverseDragResize extends CustomElement {

+ 1 - 1
src/plugins/minimize/components/minimized-chat.js

@@ -1,5 +1,5 @@
 import tpl_trimmed_chat from "../templates/trimmed_chat.js";
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { api, _converse } from "@converse/headless/core";
 import { maximize } from  '../utils.js';
 

+ 1 - 1
src/plugins/muc-views/adhoc-commands.js

@@ -1,7 +1,7 @@
 import 'shared/autocomplete/index.js';
 import log from "@converse/headless/log";
 import tpl_adhoc from './templates/ad-hoc.js';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { api, converse } from "@converse/headless/core";
 import { fetchCommandForm } from './utils.js';

+ 1 - 1
src/plugins/muc-views/chatarea.js

@@ -1,6 +1,6 @@
 import debounce from 'lodash-es/debounce';
 import tpl_muc_chatarea from './templates/muc-chatarea.js';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from '@converse/headless/core';
 

+ 1 - 1
src/plugins/muc-views/config-form.js

@@ -1,6 +1,6 @@
 import log from "@converse/headless/log";
 import tpl_muc_config_form from "./templates/muc-config-form.js";
-import { CustomElement } from 'components/element';
+import { CustomElement } from 'shared/components/element';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
 

+ 1 - 1
src/plugins/muc-views/destroyed.js

@@ -1,5 +1,5 @@
 import tpl_muc_destroyed from './templates/muc-destroyed.js';
-import { CustomElement } from 'components/element';
+import { CustomElement } from 'shared/components/element';
 import { _converse, api } from "@converse/headless/core";
 
 

+ 1 - 1
src/plugins/muc-views/disconnected.js

@@ -1,5 +1,5 @@
 import tpl_muc_disconnect from './templates/muc-disconnect.js';
-import { CustomElement } from 'components/element';
+import { CustomElement } from 'shared/components/element';
 import { __ } from 'i18n';
 import { _converse, api } from "@converse/headless/core";
 

+ 1 - 1
src/plugins/muc-views/nickname-form.js

@@ -1,5 +1,5 @@
 import tpl_muc_nickname_form from './templates/muc-nickname-form.js';
-import { CustomElement } from 'components/element';
+import { CustomElement } from 'shared/components/element';
 import { _converse, api } from "@converse/headless/core";
 
 class MUCNicknameForm extends CustomElement {

+ 1 - 1
src/plugins/muc-views/password-form.js

@@ -1,5 +1,5 @@
 import tpl_muc_password_form from "./templates/muc-password-form.js";
-import { CustomElement } from 'components/element';
+import { CustomElement } from 'shared/components/element';
 import { _converse, api } from "@converse/headless/core";
 
 

+ 1 - 1
src/plugins/muc-views/sidebar.js

@@ -1,6 +1,6 @@
 import 'shared/autocomplete/index.js';
 import tpl_muc_sidebar from "./templates/muc-sidebar.js";
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { _converse, api, converse } from "@converse/headless/core";
 
 const { u } = converse.env;

+ 2 - 2
src/plugins/muc-views/templates/muc-head.js

@@ -1,5 +1,5 @@
-import 'components/dropdown.js';
-import 'shared/rich-text.js';
+import 'shared/components/dropdown.js';
+import 'shared/components/rich-text.js';
 import { __ } from 'i18n';
 import { _converse } from "@converse/headless/core";
 import { html } from "lit-html";

+ 3 - 3
src/plugins/profile/statusview.js

@@ -1,8 +1,8 @@
+import UserSettingsModal from 'modals/user-settings';
+import tpl_profile from './templates/profile.js';
 import { ElementViewWithAvatar } from 'shared/avatar.js';
-import UserSettingsModal from "modals/user-settings";
-import tpl_profile from "./templates/profile.js";
 import { __ } from 'i18n';
-import { _converse, api } from "@converse/headless/core";
+import { _converse, api } from '@converse/headless/core';
 import { render } from 'lit-html';
 
 

+ 1 - 1
src/plugins/rootview/root.js

@@ -1,6 +1,6 @@
 import tpl_root from "./templates/root.js";
 import { api } from '@converse/headless/core';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 
 
 /**

+ 1 - 1
src/plugins/rootview/templates/root.js

@@ -1,4 +1,4 @@
-import 'components/font-awesome.js';
+import 'shared/components/font-awesome.js';
 import { api } from '@converse/headless/core';
 import { html } from 'lit-html';
 

+ 1 - 1
src/plugins/rosterview/contactview.js

@@ -2,7 +2,7 @@ import log from "@converse/headless/log";
 import tpl_pending_contact from "./templates/pending_contact.js";
 import tpl_requesting_contact from "./templates/requesting_contact.js";
 import tpl_roster_item from "./templates/roster_item.js";
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
 

+ 1 - 1
src/plugins/rosterview/templates/roster_item.js

@@ -1,7 +1,7 @@
 import { __ } from 'i18n';
 import { api } from "@converse/headless/core";
 import { html } from "lit-html";
-import { renderAvatar } from 'templates/directives/avatar';
+import { renderAvatar } from 'shared/directives/avatar';
 
 export default  (o) => {
    const i18n_chat = __('Click to chat with %1$s (XMPP address: %2$s)', o.display_name, o.jid);

+ 1 - 1
src/shared/autocomplete/component.js

@@ -1,5 +1,5 @@
 import AutoComplete from './autocomplete.js';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { FILTER_CONTAINS, FILTER_STARTSWITH } from './utils.js';
 import { api } from '@converse/headless/core';
 import { html } from 'lit-element';

+ 2 - 2
src/shared/avatar.js

@@ -1,5 +1,5 @@
-import { ElementView } from "@converse/skeletor/src/element";
-import tpl_avatar from 'templates/avatar.js';
+import tpl_avatar from 'shared/templates/avatar.js';
+import { ElementView } from '@converse/skeletor/src/element';
 import { View } from '@converse/skeletor/src/view';
 import { converse } from '@converse/headless/core';
 

+ 1 - 1
src/shared/chat/chat-content.js

@@ -1,6 +1,6 @@
 import "./message-history";
 import debounce from 'lodash/debounce';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { _converse, api } from "@converse/headless/core";
 import { html } from 'lit-element';
 

+ 1 - 1
src/shared/chat/emoji-picker-content.js

@@ -1,4 +1,4 @@
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { _converse, converse, api } from "@converse/headless/core";
 import { html } from "lit-element";
 import { tpl_all_emojis, tpl_search_results } from "./templates/emoji-picker.js";

+ 2 - 2
src/shared/chat/emoji-picker.js

@@ -1,8 +1,8 @@
 import "./emoji-picker-content.js";
 import DOMNavigator from "shared/dom-navigator";
 import debounce from 'lodash/debounce';
-import { BaseDropdown } from "components/dropdown.js";
-import { CustomElement } from 'components/element.js';
+import { BaseDropdown } from "shared/components/dropdown.js";
+import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
 import { html } from "lit-element";

+ 2 - 2
src/shared/chat/help-messages.js

@@ -1,6 +1,6 @@
-import 'components/icons.js';
+import 'shared/components/icons.js';
 import xss from 'xss/dist/xss';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { api } from '@converse/headless/core';
 import { html } from 'lit-element';
 import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';

+ 1 - 1
src/shared/chat/message-actions.js

@@ -1,5 +1,5 @@
 import log from '@converse/headless/log';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
 import { html } from 'lit-element';

+ 1 - 1
src/shared/chat/message-body.js

@@ -1,7 +1,7 @@
 import 'shared/registry.js';
 import ImageModal from 'modals/image.js';
 import renderRichText from 'shared/directives/rich-text.js';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { api } from "@converse/headless/core";
 
 

+ 1 - 1
src/shared/chat/message-history.js

@@ -1,7 +1,7 @@
 import "./message";
 import dayjs from 'dayjs';
 import tpl_new_day from "templates/new_day.js";
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { _converse, api } from "@converse/headless/core";
 import { html } from 'lit-element';
 import { repeat } from 'lit-html/directives/repeat.js';

+ 4 - 4
src/shared/chat/message.js

@@ -1,7 +1,7 @@
-import 'shared/registry';
-import 'components/dropdown.js';
 import './message-actions.js';
 import './message-body.js';
+import 'shared/components/dropdown.js';
+import 'shared/registry';
 import MessageVersionsModal from 'modals/message-versions.js';
 import OccupantModal from 'modals/occupant.js';
 import UserDetailsModal from 'modals/user-details.js';
@@ -9,12 +9,12 @@ import dayjs from 'dayjs';
 import filesize from 'filesize';
 import tpl_message from './templates/message.js';
 import tpl_spinner from 'templates/spinner.js';
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from  '@converse/headless/core';
 import { getDerivedMessageProps } from './message-history';
 import { html } from 'lit-element';
-import { renderAvatar } from 'templates/directives/avatar';
+import { renderAvatar } from 'shared/directives/avatar';
 
 const { Strophe } = converse.env;
 const u = converse.env.utils;

+ 1 - 1
src/shared/chat/templates/message.js

@@ -1,7 +1,7 @@
 import 'shared/chat/unfurl';
 import { __ } from 'i18n';
 import { html } from "lit-html";
-import { renderAvatar } from 'templates/directives/avatar';
+import { renderAvatar } from 'shared/directives/avatar';
 
 
 export default (o) => {

+ 1 - 1
src/shared/chat/toolbar.js

@@ -1,5 +1,5 @@
 import "./emoji-picker.js";
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { __ } from 'i18n';
 import { _converse, api, converse } from "@converse/headless/core";
 import { html } from 'lit-element';

+ 1 - 1
src/shared/chat/unfurl.js

@@ -1,4 +1,4 @@
-import { CustomElement } from 'components/element.js';
+import { CustomElement } from 'shared/components/element.js';
 import { _converse, api } from "@converse/headless/core";
 import tpl_unfurl from './templates/unfurl.js';
 

+ 0 - 0
src/components/brand-byline.js → src/shared/components/brand-byline.js


+ 0 - 0
src/components/brand-heading.js → src/shared/components/brand-heading.js


+ 0 - 0
src/components/brand-logo.js → src/shared/components/brand-logo.js


+ 1 - 1
src/components/dropdown.js → src/shared/components/dropdown.js

@@ -1,4 +1,4 @@
-import DOMNavigator from "../shared/dom-navigator.js";
+import DOMNavigator from "shared/dom-navigator.js";
 import { CustomElement } from './element.js';
 import { converse, api } from "@converse/headless/core";
 import { html } from 'lit-element';

+ 0 - 0
src/components/element.js → src/shared/components/element.js


+ 0 - 0
src/components/font-awesome.js → src/shared/components/font-awesome.js


+ 0 - 0
src/components/icons.js → src/shared/components/icons.js


+ 2 - 2
src/components/image-picker.js → src/shared/components/image-picker.js

@@ -1,7 +1,7 @@
 import { CustomElement } from './element.js';
-import { __ } from '../i18n';
+import { __ } from 'i18n';
 import { html } from 'lit-element';
-import { renderAvatar } from "../templates/directives/avatar.js";
+import { renderAvatar } from "shared/directives/avatar.js";
 import { api } from "@converse/headless/core";
 
 const i18n_alt_avatar = __('Your avatar image');

+ 43 - 0
src/shared/components/rich-text.js

@@ -0,0 +1,43 @@
+import renderRichText from 'shared/directives/rich-text.js';
+import { CustomElement } from 'shared/components/element.js';
+import { api } from "@converse/headless/core";
+
+export default class RichText extends CustomElement {
+
+    static get properties () {
+        return {
+            mentions: { type: Array },
+            nick: { type: String },
+            offset: { type: Number },
+            onImgClick: { type: Function },
+            onImgLoad: { type: Function },
+            render_styling: { type: Boolean },
+            show_images: { type: Boolean },
+            show_me_message: { type: Boolean },
+            text: { type: String },
+        }
+    }
+
+    constructor () {
+        super();
+        this.offset = 0;
+        this.mentions = [];
+        this.render_styling = false;
+        this.show_images = false;
+        this.show_me_message = false;
+    }
+
+    render () {
+        const options = {
+            nick: this.nick,
+            onImgClick: this.onImgClick,
+            onImgLoad: this.onImgLoad,
+            render_styling: this.render_styling,
+            show_images: this.show_images,
+            show_me_message: this.show_me_message,
+        }
+        return renderRichText(this.text, this.offset, this.mentions, options);
+    }
+}
+
+api.elements.define('converse-rich-text', RichText);

+ 1 - 1
src/templates/directives/avatar.js → src/shared/directives/avatar.js

@@ -1,4 +1,4 @@
-import tpl_avatar from '../avatar.js';
+import tpl_avatar from 'shared/templates/avatar.js';
 import { directive } from "lit-html";
 
 

+ 0 - 0
src/templates/directives/image.js → src/shared/directives/image.js


+ 0 - 0
src/templates/directives/retraction.js → src/shared/directives/retraction.js


+ 1 - 1
src/shared/directives/rich-text.js

@@ -1,4 +1,4 @@
-import { RichText } from 'shared/message/text.js';
+import { RichText } from 'shared/rich-text.js';
 import { directive, html } from "lit-html";
 import { until } from 'lit-html/directives/until.js';
 

+ 1 - 1
src/templates/directives/styling.js → src/shared/directives/styling.js

@@ -1,4 +1,4 @@
-import { RichText } from '../../shared/message/text.js';
+import { RichText } from 'shared/rich-text.js';
 import { directive, html } from 'lit-html';
 import { until } from 'lit-html/directives/until.js';
 

+ 0 - 323
src/shared/message/text.js

@@ -1,323 +0,0 @@
-import URI from 'urijs';
-import log from '@converse/headless/log';
-import { _converse, api, converse } from '@converse/headless/core';
-import { containsDirectives, getDirectiveAndLength, getDirectiveTemplate, isQuoteDirective } from './styling.js';
-import { convertASCII2Emoji, getCodePointReferences, getEmojiMarkup, getShortnameReferences } from '@converse/headless/plugins/emoji/index.js';
-import { html } from 'lit-html';
-
-const u = converse.env.utils;
-
-const isString = (s) => typeof s === 'string';
-
-// We don't render more than two line-breaks, replace extra line-breaks with
-// the zero-width whitespace character
-const collapseLineBreaks = text => text.replace(/\n\n+/g, m => `\n${"\u200B".repeat(m.length-2)}\n`);
-
-const tpl_mention_with_nick = (o) => html`<span class="mention mention--self badge badge-info">${o.mention}</span>`;
-const tpl_mention = (o) => html`<span class="mention">${o.mention}</span>`;
-
-
-/**
- * @class RichText
- * A String subclass that is used to render rich text (i.e. text that contains
- * hyperlinks, images, mentions, styling etc.).
- *
- * The "rich" parts of the text is represented by lit-html TemplateResult
- * objects which are added via the {@link RichText.addTemplateResult}
- * method and saved as metadata.
- *
- * By default Converse adds TemplateResults to support emojis, hyperlinks,
- * images, map URIs and mentions.
- *
- * 3rd party plugins can listen for the `beforeMessageBodyTransformed`
- * and/or `afterMessageBodyTransformed` events and then call
- * `addTemplateResult` on the RichText instance in order to add their own
- * rich features.
- */
-export class RichText extends String {
-
-    /**
-     * Create a new {@link RichText} instance.
-     * @param { String } text - The text to be annotated
-     * @param { Integer } offset - The offset of this particular piece of text
-     *  from the start of the original message text. This is necessary because
-     *  RichText instances can be nested when templates call directives
-     *  which create new RichText instances (as happens with XEP-393 styling directives).
-     * @param { Array } mentions - An array of mention references
-     * @param { Object } options
-     * @param { String } options.nick - The current user's nickname (only relevant if the message is in a XEP-0045 MUC)
-     * @param { Boolean } options.render_styling - Whether XEP-0393 message styling should be applied to the message
-     * @param { Boolean } options.show_images - Whether image URLs should be rendered as <img> tags.
-     * @param { Boolean } options.show_me_message - Whether /me messages should be rendered differently
-     * @param { Function } options.onImgClick - Callback for when an inline rendered image has been clicked
-     * @param { Function } options.onImgLoad - Callback for when an inline rendered image has been loaded
-     */
-    constructor (text, offset=0, mentions=[], options={}) {
-        super(text);
-        this.mentions = mentions;
-        this.nick = options?.nick;
-        this.offset = offset;
-        this.onImgClick = options?.onImgClick;
-        this.onImgLoad = options?.onImgLoad;
-        this.options = options;
-        this.payload = [];
-        this.references = [];
-        this.render_styling = options?.render_styling;
-        this.show_images = options?.show_images;
-    }
-
-    /**
-     * Look for `http` URIs and return templates that render them as URL links
-     * @param { String } text
-     * @param { Integer } offset - The index of the passed in text relative to
-     *  the start of the message body text.
-     */
-    addHyperlinks (text, offset) {
-        const objs = [];
-        try {
-            const parse_options = { 'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi };
-            URI.withinString(text, (url, start, end) => {
-                objs.push({url, start, end})
-                return url;
-            } , parse_options);
-        } catch (error) {
-            log.debug(error);
-            return;
-        }
-        objs.forEach(url_obj => {
-            const url_text = text.slice(url_obj.start, url_obj.end);
-            const filtered_url = u.filterQueryParamsFromURL(url_text);
-            this.addTemplateResult(
-                url_obj.start+offset,
-                url_obj.end+offset,
-                this.show_images && u.isImageURL(url_text) && u.isImageDomainAllowed(url_text) ?
-                    u.convertToImageTag(filtered_url, this.onImgLoad, this.onImgClick) :
-                    u.convertUrlToHyperlink(filtered_url),
-            );
-        });
-    }
-
-    /**
-     * Look for `geo` URIs and return templates that render them as URL links
-     * @param { String } text
-     * @param { Integer } offset - The index of the passed in text relative to
-     *  the start of the message body text.
-     */
-    addMapURLs (text, offset) {
-        const regex = /geo:([\-0-9.]+),([\-0-9.]+)(?:,([\-0-9.]+))?(?:\?(.*))?/g;
-        const matches = text.matchAll(regex);
-        for (const m of matches) {
-            this.addTemplateResult(
-                m.index+offset,
-                m.index+m[0].length+offset,
-                u.convertUrlToHyperlink(m[0].replace(regex, _converse.geouri_replacement))
-            );
-        }
-    }
-
-    /**
-     * Look for emojis (shortnames or unicode) and add templates for rendering them.
-     * @param { String } text
-     * @param { Integer } offset - The index of the passed in text relative to
-     *  the start of the message body text.
-     */
-    addEmojis (text, offset) {
-        const references = [...getShortnameReferences(text.toString()), ...getCodePointReferences(text.toString())];
-        references.forEach(e => {
-            this.addTemplateResult(
-                e.begin+offset,
-                e.end+offset,
-                getEmojiMarkup(e, {'add_title_wrapper': true})
-            );
-        });
-    }
-
-    /**
-     * Look for mentions included as XEP-0372 references and add templates for
-     * rendering them.
-     * @param { String } text
-     * @param { Integer } local_offset - The index of the passed in text relative to
-     *  the start of this RichText instance (which is not necessarily the same as the
-     *  offset from the start of the original message stanza's body text).
-     */
-    addMentions (text, local_offset) {
-        const full_offset = local_offset+this.offset;
-        this.mentions?.forEach(ref => {
-            const begin = Number(ref.begin)-full_offset;
-            if (begin < 0 || begin >= full_offset+text.length) {
-                return;
-            }
-            const end = Number(ref.end)-full_offset;
-            const mention = text.slice(begin, end);
-            if (mention === this.nick) {
-                this.addTemplateResult(
-                    begin+local_offset,
-                    end+local_offset,
-                    tpl_mention_with_nick({mention})
-                );
-            } else {
-                this.addTemplateResult(
-                    begin+local_offset,
-                    end+local_offset,
-                    tpl_mention({mention})
-                );
-            }
-        });
-    }
-
-    /**
-     * Look for XEP-0393 styling directives and add templates for rendering
-     * them.
-     */
-    addStyling () {
-        let i = 0;
-        const references = [];
-        if (containsDirectives(this)) {
-            while (i < this.length) {
-                const { d, length } = getDirectiveAndLength(this, i);
-                if (d && length) {
-                    const is_quote = isQuoteDirective(d);
-                    const end = i+length;
-                    const slice_end = is_quote ? end : end-d.length;
-                    let slice_begin = d === '```' ? i+d.length+1 : i+d.length;
-                    if (is_quote && this[slice_begin] === ' ') {
-                        // Trim leading space inside codeblock
-                        slice_begin += 1;
-                    }
-                    const offset = slice_begin;
-                    const text = this.slice(slice_begin, slice_end);
-                    references.push({
-                        'begin': i,
-                        'template': getDirectiveTemplate(d, text, offset, this.mentions, this.options),
-                        end,
-                    });
-                    i = end;
-                }
-                i++;
-            }
-        }
-        references.forEach(ref => this.addTemplateResult(ref.begin, ref.end, ref.template));
-    }
-
-    trimMeMessage () {
-        if (this.offset === 0) {
-            // Subtract `/me ` from 3rd person messages
-            if (this.isMeCommand()) {
-                this.payload[0] = this.payload[0].substring(4);
-            }
-        }
-    }
-
-
-    /**
-     * Look for plaintext (i.e. non-templated) sections of this RichText
-     * instance and add references via the passed in function.
-     * @param { Function } func
-     */
-    addAnnotations (func) {
-        const payload = this.marshall();
-        let idx = 0; // The text index of the element in the payload
-        for (const text of payload) {
-            if (!text) {
-                continue
-            } else if (isString(text)) {
-                func.call(this, text, idx);
-                idx += text.length;
-            } else {
-                idx = text.end;
-            }
-        }
-    }
-
-
-    /**
-     * Parse the text and add template references for rendering the "rich" parts.
-     *
-     * @param { RichText } text
-     * @param { Boolean } show_images - Should URLs of images be rendered as `<img>` tags?
-     * @param { Function } onImgLoad
-     * @param { Function } onImgClick
-     **/
-    async addTemplates() {
-        /**
-         * Synchronous event which provides a hook for transforming a chat message's body text
-         * before the default transformations have been applied.
-         * @event _converse#beforeMessageBodyTransformed
-         * @param { RichText } text - A {@link RichText } instance. You
-         *  can call {@link RichText#addTemplateResult } on it in order to
-         *  add TemplateResult objects meant to render rich parts of the message.
-         * @example _converse.api.listen.on('beforeMessageBodyTransformed', (view, text) => { ... });
-         */
-        await api.trigger('beforeMessageBodyTransformed', this, {'Synchronous': true});
-
-        this.render_styling && this.addStyling();
-        this.addAnnotations(this.addMentions);
-        this.addAnnotations(this.addHyperlinks);
-        this.addAnnotations(this.addMapURLs);
-
-        await api.emojis.initialize();
-        this.addAnnotations(this.addEmojis);
-
-        /**
-         * Synchronous event which provides a hook for transforming a chat message's body text
-         * after the default transformations have been applied.
-         * @event _converse#afterMessageBodyTransformed
-         * @param { RichText } text - A {@link RichText } instance. You
-         *  can call {@link RichText#addTemplateResult} on it in order to
-         *  add TemplateResult objects meant to render rich parts of the message.
-         * @example _converse.api.listen.on('afterMessageBodyTransformed', (view, text) => { ... });
-         */
-        await api.trigger('afterMessageBodyTransformed', this, {'Synchronous': true});
-
-        this.payload = this.marshall();
-        this.options.show_me_message && this.trimMeMessage();
-        this.payload = this.payload.map(item => isString(item) ? item : item.template);
-    }
-
-    /**
-     * The "rich" markup parts of a chat message are represented by lit-html
-     * TemplateResult objects.
-     *
-     * This method can be used to add new template results to this message's
-     * text.
-     *
-     * @method RichText.addTemplateResult
-     * @param { Number } begin - The starting index of the plain message text
-     * which is being replaced with markup.
-     * @param { Number } end - The ending index of the plain message text
-     * which is being replaced with markup.
-     * @param { Object } template - The lit-html TemplateResult instance
-     */
-    addTemplateResult (begin, end, template) {
-        this.references.push({begin, end, template});
-    }
-
-    isMeCommand () {
-        const text = this.toString();
-        if (!text) {
-            return false;
-        }
-        return text.startsWith('/me ');
-    }
-
-    /**
-     * Take the annotations and return an array of text and TemplateResult
-     * instances to be rendered to the DOM.
-     * @method RichText#marshall
-     */
-    marshall () {
-        let list = [this.toString()];
-        this.references
-            .sort((a, b) => b.begin - a.begin)
-            .forEach(ref => {
-                const text = list.shift();
-                list = [
-                    text.slice(0, ref.begin),
-                    ref,
-                    text.slice(ref.end),
-                    ...list
-                ];
-            });
-        return list.reduce((acc, i) => isString(i) ? [...acc, convertASCII2Emoji(collapseLineBreaks(i))] : [...acc, i], []);
-    }
-}

+ 315 - 35
src/shared/rich-text.js

@@ -1,43 +1,323 @@
-import renderRichText from 'shared/directives/rich-text.js';
-import { CustomElement } from 'components/element.js';
-import { api } from "@converse/headless/core";
-
-export default class RichText extends CustomElement {
-
-    static get properties () {
-        return {
-            mentions: { type: Array },
-            nick: { type: String },
-            offset: { type: Number },
-            onImgClick: { type: Function },
-            onImgLoad: { type: Function },
-            render_styling: { type: Boolean },
-            show_images: { type: Boolean },
-            show_me_message: { type: Boolean },
-            text: { type: String },
+import URI from 'urijs';
+import log from '@converse/headless/log';
+import { _converse, api, converse } from '@converse/headless/core';
+import { containsDirectives, getDirectiveAndLength, getDirectiveTemplate, isQuoteDirective } from './styling.js';
+import { convertASCII2Emoji, getCodePointReferences, getEmojiMarkup, getShortnameReferences } from '@converse/headless/plugins/emoji/index.js';
+import { html } from 'lit-html';
+
+const u = converse.env.utils;
+
+const isString = (s) => typeof s === 'string';
+
+// We don't render more than two line-breaks, replace extra line-breaks with
+// the zero-width whitespace character
+const collapseLineBreaks = text => text.replace(/\n\n+/g, m => `\n${"\u200B".repeat(m.length-2)}\n`);
+
+const tpl_mention_with_nick = (o) => html`<span class="mention mention--self badge badge-info">${o.mention}</span>`;
+const tpl_mention = (o) => html`<span class="mention">${o.mention}</span>`;
+
+
+/**
+ * @class RichText
+ * A String subclass that is used to render rich text (i.e. text that contains
+ * hyperlinks, images, mentions, styling etc.).
+ *
+ * The "rich" parts of the text is represented by lit-html TemplateResult
+ * objects which are added via the {@link RichText.addTemplateResult}
+ * method and saved as metadata.
+ *
+ * By default Converse adds TemplateResults to support emojis, hyperlinks,
+ * images, map URIs and mentions.
+ *
+ * 3rd party plugins can listen for the `beforeMessageBodyTransformed`
+ * and/or `afterMessageBodyTransformed` events and then call
+ * `addTemplateResult` on the RichText instance in order to add their own
+ * rich features.
+ */
+export class RichText extends String {
+
+    /**
+     * Create a new {@link RichText} instance.
+     * @param { String } text - The text to be annotated
+     * @param { Integer } offset - The offset of this particular piece of text
+     *  from the start of the original message text. This is necessary because
+     *  RichText instances can be nested when templates call directives
+     *  which create new RichText instances (as happens with XEP-393 styling directives).
+     * @param { Array } mentions - An array of mention references
+     * @param { Object } options
+     * @param { String } options.nick - The current user's nickname (only relevant if the message is in a XEP-0045 MUC)
+     * @param { Boolean } options.render_styling - Whether XEP-0393 message styling should be applied to the message
+     * @param { Boolean } options.show_images - Whether image URLs should be rendered as <img> tags.
+     * @param { Boolean } options.show_me_message - Whether /me messages should be rendered differently
+     * @param { Function } options.onImgClick - Callback for when an inline rendered image has been clicked
+     * @param { Function } options.onImgLoad - Callback for when an inline rendered image has been loaded
+     */
+    constructor (text, offset=0, mentions=[], options={}) {
+        super(text);
+        this.mentions = mentions;
+        this.nick = options?.nick;
+        this.offset = offset;
+        this.onImgClick = options?.onImgClick;
+        this.onImgLoad = options?.onImgLoad;
+        this.options = options;
+        this.payload = [];
+        this.references = [];
+        this.render_styling = options?.render_styling;
+        this.show_images = options?.show_images;
+    }
+
+    /**
+     * Look for `http` URIs and return templates that render them as URL links
+     * @param { String } text
+     * @param { Integer } offset - The index of the passed in text relative to
+     *  the start of the message body text.
+     */
+    addHyperlinks (text, offset) {
+        const objs = [];
+        try {
+            const parse_options = { 'start': /\b(?:([a-z][a-z0-9.+-]*:\/\/)|xmpp:|mailto:|www\.)/gi };
+            URI.withinString(text, (url, start, end) => {
+                objs.push({url, start, end})
+                return url;
+            } , parse_options);
+        } catch (error) {
+            log.debug(error);
+            return;
         }
+        objs.forEach(url_obj => {
+            const url_text = text.slice(url_obj.start, url_obj.end);
+            const filtered_url = u.filterQueryParamsFromURL(url_text);
+            this.addTemplateResult(
+                url_obj.start+offset,
+                url_obj.end+offset,
+                this.show_images && u.isImageURL(url_text) && u.isImageDomainAllowed(url_text) ?
+                    u.convertToImageTag(filtered_url, this.onImgLoad, this.onImgClick) :
+                    u.convertUrlToHyperlink(filtered_url),
+            );
+        });
     }
 
-    constructor () {
-        super();
-        this.offset = 0;
-        this.mentions = [];
-        this.render_styling = false;
-        this.show_images = false;
-        this.show_me_message = false;
+    /**
+     * Look for `geo` URIs and return templates that render them as URL links
+     * @param { String } text
+     * @param { Integer } offset - The index of the passed in text relative to
+     *  the start of the message body text.
+     */
+    addMapURLs (text, offset) {
+        const regex = /geo:([\-0-9.]+),([\-0-9.]+)(?:,([\-0-9.]+))?(?:\?(.*))?/g;
+        const matches = text.matchAll(regex);
+        for (const m of matches) {
+            this.addTemplateResult(
+                m.index+offset,
+                m.index+m[0].length+offset,
+                u.convertUrlToHyperlink(m[0].replace(regex, _converse.geouri_replacement))
+            );
+        }
+    }
+
+    /**
+     * Look for emojis (shortnames or unicode) and add templates for rendering them.
+     * @param { String } text
+     * @param { Integer } offset - The index of the passed in text relative to
+     *  the start of the message body text.
+     */
+    addEmojis (text, offset) {
+        const references = [...getShortnameReferences(text.toString()), ...getCodePointReferences(text.toString())];
+        references.forEach(e => {
+            this.addTemplateResult(
+                e.begin+offset,
+                e.end+offset,
+                getEmojiMarkup(e, {'add_title_wrapper': true})
+            );
+        });
+    }
+
+    /**
+     * Look for mentions included as XEP-0372 references and add templates for
+     * rendering them.
+     * @param { String } text
+     * @param { Integer } local_offset - The index of the passed in text relative to
+     *  the start of this RichText instance (which is not necessarily the same as the
+     *  offset from the start of the original message stanza's body text).
+     */
+    addMentions (text, local_offset) {
+        const full_offset = local_offset+this.offset;
+        this.mentions?.forEach(ref => {
+            const begin = Number(ref.begin)-full_offset;
+            if (begin < 0 || begin >= full_offset+text.length) {
+                return;
+            }
+            const end = Number(ref.end)-full_offset;
+            const mention = text.slice(begin, end);
+            if (mention === this.nick) {
+                this.addTemplateResult(
+                    begin+local_offset,
+                    end+local_offset,
+                    tpl_mention_with_nick({mention})
+                );
+            } else {
+                this.addTemplateResult(
+                    begin+local_offset,
+                    end+local_offset,
+                    tpl_mention({mention})
+                );
+            }
+        });
     }
 
-    render () {
-        const options = {
-            nick: this.nick,
-            onImgClick: this.onImgClick,
-            onImgLoad: this.onImgLoad,
-            render_styling: this.render_styling,
-            show_images: this.show_images,
-            show_me_message: this.show_me_message,
+    /**
+     * Look for XEP-0393 styling directives and add templates for rendering
+     * them.
+     */
+    addStyling () {
+        let i = 0;
+        const references = [];
+        if (containsDirectives(this)) {
+            while (i < this.length) {
+                const { d, length } = getDirectiveAndLength(this, i);
+                if (d && length) {
+                    const is_quote = isQuoteDirective(d);
+                    const end = i+length;
+                    const slice_end = is_quote ? end : end-d.length;
+                    let slice_begin = d === '```' ? i+d.length+1 : i+d.length;
+                    if (is_quote && this[slice_begin] === ' ') {
+                        // Trim leading space inside codeblock
+                        slice_begin += 1;
+                    }
+                    const offset = slice_begin;
+                    const text = this.slice(slice_begin, slice_end);
+                    references.push({
+                        'begin': i,
+                        'template': getDirectiveTemplate(d, text, offset, this.mentions, this.options),
+                        end,
+                    });
+                    i = end;
+                }
+                i++;
+            }
+        }
+        references.forEach(ref => this.addTemplateResult(ref.begin, ref.end, ref.template));
+    }
+
+    trimMeMessage () {
+        if (this.offset === 0) {
+            // Subtract `/me ` from 3rd person messages
+            if (this.isMeCommand()) {
+                this.payload[0] = this.payload[0].substring(4);
+            }
         }
-        return renderRichText(this.text, this.offset, this.mentions, options);
     }
-}
 
-api.elements.define('converse-rich-text', RichText);
+
+    /**
+     * Look for plaintext (i.e. non-templated) sections of this RichText
+     * instance and add references via the passed in function.
+     * @param { Function } func
+     */
+    addAnnotations (func) {
+        const payload = this.marshall();
+        let idx = 0; // The text index of the element in the payload
+        for (const text of payload) {
+            if (!text) {
+                continue
+            } else if (isString(text)) {
+                func.call(this, text, idx);
+                idx += text.length;
+            } else {
+                idx = text.end;
+            }
+        }
+    }
+
+
+    /**
+     * Parse the text and add template references for rendering the "rich" parts.
+     *
+     * @param { RichText } text
+     * @param { Boolean } show_images - Should URLs of images be rendered as `<img>` tags?
+     * @param { Function } onImgLoad
+     * @param { Function } onImgClick
+     **/
+    async addTemplates() {
+        /**
+         * Synchronous event which provides a hook for transforming a chat message's body text
+         * before the default transformations have been applied.
+         * @event _converse#beforeMessageBodyTransformed
+         * @param { RichText } text - A {@link RichText } instance. You
+         *  can call {@link RichText#addTemplateResult } on it in order to
+         *  add TemplateResult objects meant to render rich parts of the message.
+         * @example _converse.api.listen.on('beforeMessageBodyTransformed', (view, text) => { ... });
+         */
+        await api.trigger('beforeMessageBodyTransformed', this, {'Synchronous': true});
+
+        this.render_styling && this.addStyling();
+        this.addAnnotations(this.addMentions);
+        this.addAnnotations(this.addHyperlinks);
+        this.addAnnotations(this.addMapURLs);
+
+        await api.emojis.initialize();
+        this.addAnnotations(this.addEmojis);
+
+        /**
+         * Synchronous event which provides a hook for transforming a chat message's body text
+         * after the default transformations have been applied.
+         * @event _converse#afterMessageBodyTransformed
+         * @param { RichText } text - A {@link RichText } instance. You
+         *  can call {@link RichText#addTemplateResult} on it in order to
+         *  add TemplateResult objects meant to render rich parts of the message.
+         * @example _converse.api.listen.on('afterMessageBodyTransformed', (view, text) => { ... });
+         */
+        await api.trigger('afterMessageBodyTransformed', this, {'Synchronous': true});
+
+        this.payload = this.marshall();
+        this.options.show_me_message && this.trimMeMessage();
+        this.payload = this.payload.map(item => isString(item) ? item : item.template);
+    }
+
+    /**
+     * The "rich" markup parts of a chat message are represented by lit-html
+     * TemplateResult objects.
+     *
+     * This method can be used to add new template results to this message's
+     * text.
+     *
+     * @method RichText.addTemplateResult
+     * @param { Number } begin - The starting index of the plain message text
+     * which is being replaced with markup.
+     * @param { Number } end - The ending index of the plain message text
+     * which is being replaced with markup.
+     * @param { Object } template - The lit-html TemplateResult instance
+     */
+    addTemplateResult (begin, end, template) {
+        this.references.push({begin, end, template});
+    }
+
+    isMeCommand () {
+        const text = this.toString();
+        if (!text) {
+            return false;
+        }
+        return text.startsWith('/me ');
+    }
+
+    /**
+     * Take the annotations and return an array of text and TemplateResult
+     * instances to be rendered to the DOM.
+     * @method RichText#marshall
+     */
+    marshall () {
+        let list = [this.toString()];
+        this.references
+            .sort((a, b) => b.begin - a.begin)
+            .forEach(ref => {
+                const text = list.shift();
+                list = [
+                    text.slice(0, ref.begin),
+                    ref,
+                    text.slice(ref.end),
+                    ...list
+                ];
+            });
+        return list.reduce((acc, i) => isString(i) ? [...acc, convertASCII2Emoji(collapseLineBreaks(i))] : [...acc, i], []);
+    }
+}

+ 1 - 1
src/shared/message/styling.js → src/shared/styling.js

@@ -5,7 +5,7 @@
  * @todo Other parsing helpers can be made more abstract and placed here.
  */
 import { html } from 'lit-element';
-import { renderStylingDirectiveBody } from '../../templates/directives/styling.js';
+import { renderStylingDirectiveBody } from 'shared/directives/styling.js';
 
 
 const styling_directives = ['*', '_', '~', '`', '```', '>'];

+ 0 - 0
src/templates/avatar.js → src/shared/templates/avatar.js


+ 1 - 1
src/templates/image.js

@@ -1,4 +1,4 @@
 import { html } from "lit-html";
-import { renderImage } from "./directives/image.js";
+import { renderImage } from "shared/directives/image.js";
 
 export default (o) => html`${renderImage(o.url, o.url, o.onLoad, o.onClick)}`;