浏览代码

Created a web component registry and exports components as modules in order to facilitate their customization

Ariel Fuggini 5 年之前
父节点
当前提交
82357f7d97

+ 2 - 2
src/components/adhoc-commands.js

@@ -122,7 +122,7 @@ async function fetchCommandForm (command) {
 }
 
 
-export class AdHocCommands extends CustomElement {
+export default class AdHocCommands extends CustomElement {
 
     static get properties () {
         return {
@@ -239,4 +239,4 @@ export class AdHocCommands extends CustomElement {
     }
 }
 
-window.customElements.define('converse-adhoc-commands', AdHocCommands);
+api.elements.define('converse-adhoc-commands', AdHocCommands);

+ 3 - 3
src/components/autocomplete.js

@@ -1,9 +1,9 @@
 import { AutoComplete, FILTER_CONTAINS, FILTER_STARTSWITH } from "../converse-autocomplete.js";
 import { CustomElement } from './element.js';
 import { html } from 'lit-element';
+import { api } from "@converse/headless/converse-core";
 
-
-export class AutoCompleteComponent extends CustomElement {
+export default class AutoCompleteComponent extends CustomElement {
 
     static get properties () {
         return {
@@ -70,4 +70,4 @@ export class AutoCompleteComponent extends CustomElement {
     }
 }
 
-window.customElements.define('converse-autocomplete', AutoCompleteComponent);
+api.elements.define('converse-autocomplete', AutoCompleteComponent);

+ 3 - 3
src/components/chat_content.js

@@ -3,9 +3,9 @@ import xss from "xss/dist/xss";
 import { CustomElement } from './element.js';
 import { html } from 'lit-element';
 import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
+import { api } from "@converse/headless/converse-core";
 
-
-class ChatContent extends CustomElement {
+export default class ChatContent extends CustomElement {
 
     static get properties () {
         return {
@@ -27,4 +27,4 @@ class ChatContent extends CustomElement {
     }
 }
 
-customElements.define('converse-chat-content', ChatContent);
+api.elements.define('converse-chat-content', ChatContent);

+ 3 - 3
src/components/dropdown.js

@@ -1,6 +1,6 @@
 import DOMNavigator from "../dom-navigator";
 import { CustomElement } from './element.js';
-import { converse } from "@converse/headless/converse-core";
+import { converse, api } from "@converse/headless/converse-core";
 import { html } from 'lit-element';
 import { until } from 'lit-html/directives/until.js';
 
@@ -48,7 +48,7 @@ export class BaseDropdown extends CustomElement {
 }
 
 
-export class DropdownList extends BaseDropdown {
+export default class DropdownList extends BaseDropdown {
 
     static get properties () {
         return {
@@ -109,4 +109,4 @@ export class DropdownList extends BaseDropdown {
     }
 }
 
-window.customElements.define('converse-dropdown', DropdownList);
+api.elements.define('converse-dropdown', DropdownList);

+ 107 - 0
src/components/emoji-picker-content.js

@@ -0,0 +1,107 @@
+import sizzle from 'sizzle';
+import { CustomElement } from './element.js';
+import { _converse, api } from "@converse/headless/converse-core";
+import { html } from "lit-element";
+import { tpl_all_emojis, tpl_search_results } from "../templates/emoji_picker.js";
+
+
+export default class EmojiPickerContent extends CustomElement {
+  static get properties () {
+      return {
+          'chatview': { type: Object },
+          'search_results': { type: Array },
+          'current_skintone': { type: String },
+          'model': { type: Object },
+          'query': { type: String },
+      }
+  }
+
+  render () {
+      const props = {
+          'current_skintone': this.current_skintone,
+          'insertEmoji': ev => this.insertEmoji(ev),
+          'query': this.query,
+          'search_results': this.search_results,
+          'shouldBeHidden': shortname => this.shouldBeHidden(shortname),
+      }
+      return html`
+          <div class="emoji-picker__lists">
+              ${tpl_search_results(props)}
+              ${tpl_all_emojis(props)}
+          </div>
+      `;
+  }
+
+  firstUpdated () {
+      this.initIntersectionObserver();
+  }
+
+  initIntersectionObserver () {
+      if (!window.IntersectionObserver) {
+          return;
+      }
+      if (this.observer) {
+          this.observer.disconnect();
+      } else {
+          const options = {
+              root: this.querySelector('.emoji-picker__lists'),
+              threshold: [0.1]
+          }
+          const handler = ev => this.setCategoryOnVisibilityChange(ev);
+          this.observer = new IntersectionObserver(handler, options);
+      }
+      sizzle('.emoji-picker', this).forEach(a => this.observer.observe(a));
+  }
+
+  setCategoryOnVisibilityChange (ev) {
+      const selected = this.parentElement.navigator.selected;
+      const intersection_with_selected = ev.filter(i => i.target.contains(selected)).pop();
+      let current;
+      // Choose the intersection that contains the currently selected
+      // element, or otherwise the one with the largest ratio.
+      if (intersection_with_selected) {
+          current = intersection_with_selected;
+      } else {
+          current = ev.reduce((p, c) => c.intersectionRatio >= (p?.intersectionRatio || 0) ? c : p, null);
+      }
+      if (current && current.isIntersecting) {
+          const category = current.target.getAttribute('data-category');
+          if (category !== this.model.get('current_category')) {
+              this.parentElement.preserve_scroll = true;
+              this.model.save({'current_category': category});
+          }
+      }
+  }
+
+  insertEmoji (ev) {
+      ev.preventDefault();
+      ev.stopPropagation();
+      const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
+      const replace = this.model.get('autocompleting');
+      const position = this.model.get('position');
+      this.model.set({'autocompleting': null, 'position': null, 'query': ''});
+      this.chatview.insertIntoTextArea(target.getAttribute('data-emoji'), replace, false, position);
+      this.chatview.emoji_dropdown.toggle();
+  }
+
+  shouldBeHidden (shortname) {
+      // Helper method for the template which decides whether an
+      // emoji should be hidden, based on which skin tone is
+      // currently being applied.
+      if (shortname.includes('_tone')) {
+          if (!this.current_skintone || !shortname.includes(this.current_skintone)) {
+              return true;
+          }
+      } else {
+          if (this.current_skintone && _converse.emojis.toned.includes(shortname)) {
+              return true;
+          }
+      }
+      if (this.query && !_converse.FILTER_CONTAINS(shortname, this.query)) {
+          return true;
+      }
+      return false;
+  }
+}
+
+api.elements.define('converse-emoji-picker-content', EmojiPickerContent);

+ 5 - 107
src/components/emoji-picker.js

@@ -1,115 +1,14 @@
+import "./emoji-picker-content.js";
 import DOMNavigator from "../dom-navigator";
-import sizzle from 'sizzle';
 import { CustomElement } from './element.js';
-import { _converse, converse } from "@converse/headless/converse-core";
+import { _converse, api, converse } from "@converse/headless/converse-core";
 import { debounce, find } from "lodash-es";
-import { html } from "lit-element";
-import { tpl_all_emojis, tpl_emoji_picker, tpl_search_results } from "../templates/emoji_picker.js";
+import { tpl_emoji_picker } from "../templates/emoji_picker.js";
 
 const u = converse.env.utils;
 
 
-export class EmojiPickerContent extends CustomElement {
-    static get properties () {
-        return {
-            'chatview': { type: Object },
-            'search_results': { type: Array },
-            'current_skintone': { type: String },
-            'model': { type: Object },
-            'query': { type: String },
-        }
-    }
-
-    render () {
-        const props = {
-            'current_skintone': this.current_skintone,
-            'insertEmoji': ev => this.insertEmoji(ev),
-            'query': this.query,
-            'search_results': this.search_results,
-            'shouldBeHidden': shortname => this.shouldBeHidden(shortname),
-        }
-        return html`
-            <div class="emoji-picker__lists">
-                ${tpl_search_results(props)}
-                ${tpl_all_emojis(props)}
-            </div>
-        `;
-    }
-
-    firstUpdated () {
-        this.initIntersectionObserver();
-    }
-
-    initIntersectionObserver () {
-        if (!window.IntersectionObserver) {
-            return;
-        }
-        if (this.observer) {
-            this.observer.disconnect();
-        } else {
-            const options = {
-                root: this.querySelector('.emoji-picker__lists'),
-                threshold: [0.1]
-            }
-            const handler = ev => this.setCategoryOnVisibilityChange(ev);
-            this.observer = new IntersectionObserver(handler, options);
-        }
-        sizzle('.emoji-picker', this).forEach(a => this.observer.observe(a));
-    }
-
-    setCategoryOnVisibilityChange (ev) {
-        const selected = this.parentElement.navigator.selected;
-        const intersection_with_selected = ev.filter(i => i.target.contains(selected)).pop();
-        let current;
-        // Choose the intersection that contains the currently selected
-        // element, or otherwise the one with the largest ratio.
-        if (intersection_with_selected) {
-            current = intersection_with_selected;
-        } else {
-            current = ev.reduce((p, c) => c.intersectionRatio >= (p?.intersectionRatio || 0) ? c : p, null);
-        }
-        if (current && current.isIntersecting) {
-            const category = current.target.getAttribute('data-category');
-            if (category !== this.model.get('current_category')) {
-                this.parentElement.preserve_scroll = true;
-                this.model.save({'current_category': category});
-            }
-        }
-    }
-
-    insertEmoji (ev) {
-        ev.preventDefault();
-        ev.stopPropagation();
-        const target = ev.target.nodeName === 'IMG' ? ev.target.parentElement : ev.target;
-        const replace = this.model.get('autocompleting');
-        const position = this.model.get('position');
-        this.model.set({'autocompleting': null, 'position': null, 'query': ''});
-        this.chatview.insertIntoTextArea(target.getAttribute('data-emoji'), replace, false, position);
-        this.chatview.emoji_dropdown.toggle();
-    }
-
-    shouldBeHidden (shortname) {
-        // Helper method for the template which decides whether an
-        // emoji should be hidden, based on which skin tone is
-        // currently being applied.
-        if (shortname.includes('_tone')) {
-            if (!this.current_skintone || !shortname.includes(this.current_skintone)) {
-                return true;
-            }
-        } else {
-            if (this.current_skintone && _converse.emojis.toned.includes(shortname)) {
-                return true;
-            }
-        }
-        if (this.query && !_converse.FILTER_CONTAINS(shortname, this.query)) {
-            return true;
-        }
-        return false;
-    }
-}
-
-
-export class EmojiPicker extends CustomElement {
+export default class EmojiPicker extends CustomElement {
 
     static get properties () {
         return {
@@ -343,5 +242,4 @@ export class EmojiPicker extends CustomElement {
 }
 
 
-window.customElements.define('converse-emoji-picker', EmojiPicker);
-window.customElements.define('converse-emoji-picker-content', EmojiPickerContent);
+api.elements.define('converse-emoji-picker', EmojiPicker);

+ 2 - 2
src/components/help_messages.js

@@ -6,7 +6,7 @@ import { html } from 'lit-element';
 import { unsafeHTML } from 'lit-html/directives/unsafe-html.js';
 
 
-class ChatHelp extends CustomElement {
+export default class ChatHelp extends CustomElement {
 
     static get properties () {
         return {
@@ -42,4 +42,4 @@ class ChatHelp extends CustomElement {
     }
 }
 
-customElements.define('converse-chat-help', ChatHelp);
+api.elements.define('converse-chat-help', ChatHelp);

+ 3 - 2
src/components/image_picker.js

@@ -2,11 +2,12 @@ import { CustomElement } from './element.js';
 import { __ } from '@converse/headless/i18n';
 import { html } from 'lit-element';
 import { renderAvatar } from "../templates/directives/avatar.js";
+import { api } from "@converse/headless/converse-core";
 
 const i18n_alt_avatar = __('Your avatar image');
 
 
-export class ImagePicker extends CustomElement {
+export default class ImagePicker extends CustomElement {
 
     static get properties () {
         return {
@@ -43,4 +44,4 @@ export class ImagePicker extends CustomElement {
     }
 }
 
-window.customElements.define('converse-image-picker', ImagePicker);
+api.elements.define('converse-image-picker', ImagePicker);

+ 1 - 1
src/components/message-actions.js

@@ -75,4 +75,4 @@ class MessageActions extends CustomElement {
     }
 }
 
-customElements.define('converse-message-actions', MessageActions);
+api.elements.define('converse-message-actions', MessageActions);

+ 3 - 3
src/components/message-body.js

@@ -1,8 +1,8 @@
 import { CustomElement } from './element.js';
 import { renderBodyText } from './../templates/directives/body';
+import { api } from "@converse/headless/converse-core";
 
-
-class MessageBody extends CustomElement {
+export default class MessageBody extends CustomElement {
 
     static get properties () {
         return {
@@ -17,4 +17,4 @@ class MessageBody extends CustomElement {
     }
 }
 
-customElements.define('converse-chat-message-body', MessageBody);
+api.elements.define('converse-chat-message-body', MessageBody);

+ 2 - 2
src/components/message-history.js

@@ -81,7 +81,7 @@ function getHats (model) {
 }
 
 
-class MessageHistory extends CustomElement {
+export default class MessageHistory extends CustomElement {
 
     static get properties () {
         return {
@@ -122,4 +122,4 @@ class MessageHistory extends CustomElement {
     }
 }
 
-customElements.define('converse-message-history', MessageHistory);
+api.elements.define('converse-message-history', MessageHistory);

+ 2 - 2
src/components/message.js

@@ -21,7 +21,7 @@ const i18n_show_less = __('Show less');
 const i18n_uploading = __('Uploading file:');
 
 
-class Message extends CustomElement {
+export default class Message extends CustomElement {
 
     static get properties () {
         return {
@@ -265,4 +265,4 @@ class Message extends CustomElement {
     }
 }
 
-customElements.define('converse-chat-message', Message);
+api.elements.define('converse-chat-message', Message);

+ 2 - 0
src/converse-chatboxviews.js

@@ -49,6 +49,8 @@ converse.plugins.add('converse-chatboxviews', {
          * loaded by converse.js's plugin machinery.
          */
 
+        api.elements.register();
+
         api.promises.add(['chatBoxViewsInitialized']);
 
         // Configuration values for this plugin

+ 19 - 0
src/converse-registry.js

@@ -0,0 +1,19 @@
+import { _converse } from "@converse/headless/converse-core";
+
+const registry = {};
+
+function define (componentName, componentClass) {
+    this.registry[componentName] = componentClass;
+}
+
+function register () {
+    Object.keys(registry).map(componentName =>
+        window.customElements.define(componentName, registry[componentName])
+    );
+}
+
+_converse.api.elements = {
+    registry,
+    define,
+    register
+}

+ 1 - 0
src/converse.js

@@ -9,6 +9,7 @@
  * Any of the following components may be removed if they're not needed.
  */
 import "@converse/headless/headless";
+import "converse-registry";
 import "converse-autocomplete";
 import "converse-bookmark-views";  // Views for XEP-0048 Bookmarks
 import "converse-chatview";        // Renders standalone chat boxes for single user chat