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

This commit is contained in:
Ariel Fuggini 2020-07-01 14:45:18 -05:00 committed by JC Brand
parent 3607bb6dc8
commit 82357f7d97
15 changed files with 158 additions and 130 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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
src/converse-registry.js Normal file
View File

@ -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
}

View File

@ -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