Move the autocomplete code into ./shared
And remove it as a plugin.
This commit is contained in:
parent
9174be8ff3
commit
bb3f52d2f2
@ -1,4 +1,4 @@
|
||||
import "../plugins/autocomplete.js"
|
||||
import 'shared/autocomplete/index.js';
|
||||
import log from "@converse/headless/log";
|
||||
import sizzle from "sizzle";
|
||||
import { CustomElement } from './element.js';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import "../plugins/autocomplete.js"
|
||||
import 'shared/autocomplete/index.js';
|
||||
import tpl_muc_sidebar from "templates/muc_sidebar.js";
|
||||
import { CustomElement } from './element.js';
|
||||
import { api, converse } from "@converse/headless/core";
|
||||
|
@ -14,7 +14,6 @@ import "shared/registry.js";
|
||||
* --------------------
|
||||
* Any of the following components may be removed if they're not needed.
|
||||
*/
|
||||
import "./plugins/autocomplete.js";
|
||||
import "./plugins/bookmark-views.js"; // Views for XEP-0048 Bookmarks
|
||||
import "./plugins/chatview/index.js"; // Renders standalone chat boxes for single user chat
|
||||
import "./plugins/controlbox/index.js"; // The control box
|
||||
@ -40,7 +39,6 @@ import "../sass/converse.scss";
|
||||
import { converse } from "@converse/headless/core";
|
||||
|
||||
const WHITELISTED_PLUGINS = [
|
||||
'converse-autocomplete',
|
||||
'converse-bookmark-views',
|
||||
'converse-chatboxviews',
|
||||
'converse-chatview',
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'shared/autocomplete/index.js';
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_add_contact_modal from "./templates/add-contact.js";
|
||||
import { __ } from '../i18n';
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'shared/autocomplete/index.js';
|
||||
import BootstrapModal from "./base.js";
|
||||
import tpl_muc_invite_modal from "./templates/muc-invite.js";
|
||||
import { _converse, converse } from "@converse/headless/core";
|
||||
|
@ -1,5 +1,6 @@
|
||||
import './config-form.js';
|
||||
import './password-form.js';
|
||||
import 'shared/autocomplete/index.js';
|
||||
import MUCInviteModal from 'modals/muc-invite.js';
|
||||
import ModeratorToolsModal from 'modals/moderator-tools.js';
|
||||
import OccupantModal from 'modals/occupant.js';
|
||||
|
@ -1,141 +1,19 @@
|
||||
/**
|
||||
* @module converse-autocomplete
|
||||
* @copyright Lea Verou and the Converse.js contributors
|
||||
* @description
|
||||
* Converse.js plugin which started as a fork of Lea Verou's Awesomplete
|
||||
* Started as a fork of Lea Verou's "Awesomplete"
|
||||
* https://leaverou.github.io/awesomplete/
|
||||
* @license Mozilla Public License (MPLv2)
|
||||
*/
|
||||
|
||||
import { Events } from '@converse/skeletor/src/events.js';
|
||||
import { helpers, FILTER_CONTAINS, ITEM, SORT_BY_QUERY_POSITION } from './utils.js';
|
||||
import Suggestion from './suggestion.js';
|
||||
import { converse } from "@converse/headless/core";
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
|
||||
export const FILTER_CONTAINS = function (text, input) {
|
||||
return RegExp(helpers.regExpEscape(input.trim()), "i").test(text);
|
||||
};
|
||||
|
||||
|
||||
export const FILTER_STARTSWITH = function (text, input) {
|
||||
return RegExp("^" + helpers.regExpEscape(input.trim()), "i").test(text);
|
||||
};
|
||||
|
||||
|
||||
const SORT_BY_LENGTH = function (a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return a.length - b.length;
|
||||
}
|
||||
return a < b? -1 : 1;
|
||||
};
|
||||
|
||||
const SORT_BY_QUERY_POSITION = function (a, b) {
|
||||
const query = a.query.toLowerCase();
|
||||
const x = a.label.toLowerCase().indexOf(query);
|
||||
const y = b.label.toLowerCase().indexOf(query);
|
||||
|
||||
if (x === y) {
|
||||
return SORT_BY_LENGTH(a, b);
|
||||
}
|
||||
return (x === -1 ? Infinity : x) < (y === -1 ? Infinity : y) ? -1 : 1
|
||||
}
|
||||
|
||||
|
||||
const ITEM = (text, input) => {
|
||||
input = input.trim();
|
||||
const element = document.createElement("li");
|
||||
element.setAttribute("aria-selected", "false");
|
||||
|
||||
const regex = new RegExp("("+input+")", "ig");
|
||||
const parts = input ? text.split(regex) : [text];
|
||||
parts.forEach((txt) => {
|
||||
if (input && txt.match(regex)) {
|
||||
const match = document.createElement("mark");
|
||||
match.textContent = txt;
|
||||
element.appendChild(match);
|
||||
} else {
|
||||
element.appendChild(document.createTextNode(txt));
|
||||
}
|
||||
});
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
const helpers = {
|
||||
|
||||
getElement (expr, el) {
|
||||
return typeof expr === "string"? (el || document).querySelector(expr) : expr || null;
|
||||
},
|
||||
|
||||
bind (element, o) {
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
if (!Object.prototype.hasOwnProperty.call(o, event)) {
|
||||
continue;
|
||||
}
|
||||
const callback = o[event];
|
||||
event.split(/\s+/).forEach(event => element.addEventListener(event, callback));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unbind (element, o) {
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
if (!Object.prototype.hasOwnProperty.call(o, event)) {
|
||||
continue;
|
||||
}
|
||||
const callback = o[event];
|
||||
event.split(/\s+/).forEach(event => element.removeEventListener(event, callback));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
regExpEscape (s) {
|
||||
return s.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
|
||||
},
|
||||
|
||||
isMention (word, ac_triggers) {
|
||||
return (ac_triggers.includes(word[0]) || u.isMentionBoundary(word[0]) && ac_triggers.includes(word[1]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* An autocomplete suggestion
|
||||
*/
|
||||
class Suggestion extends String {
|
||||
|
||||
/**
|
||||
* @param { Any } data - The auto-complete data. Ideally an object e.g. { label, value },
|
||||
* which specifies the value and human-presentable label of the suggestion.
|
||||
* @param { string } query - The query string being auto-completed
|
||||
*/
|
||||
constructor (data, query) {
|
||||
super();
|
||||
const o = Array.isArray(data)
|
||||
? { label: data[0], value: data[1] }
|
||||
: typeof data === "object" && "label" in data && "value" in data ? data : { label: data, value: data };
|
||||
|
||||
this.label = o.label || o.value;
|
||||
this.value = o.value;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
get lenth () {
|
||||
return this.label.length;
|
||||
}
|
||||
|
||||
toString () {
|
||||
return "" + this.label;
|
||||
}
|
||||
|
||||
valueOf () {
|
||||
return this.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AutoComplete {
|
||||
|
||||
constructor (el, config={}) {
|
||||
@ -433,13 +311,4 @@ export class AutoComplete {
|
||||
// Make it an event emitter
|
||||
Object.assign(AutoComplete.prototype, Events);
|
||||
|
||||
|
||||
converse.plugins.add("converse-autocomplete", {
|
||||
|
||||
initialize () {
|
||||
const _converse = this._converse;
|
||||
_converse.FILTER_CONTAINS = FILTER_CONTAINS;
|
||||
_converse.FILTER_STARTSWITH = FILTER_STARTSWITH;
|
||||
_converse.AutoComplete = AutoComplete;
|
||||
}
|
||||
});
|
||||
export default AutoComplete;
|
@ -1,10 +1,10 @@
|
||||
import { AutoComplete, FILTER_CONTAINS, FILTER_STARTSWITH } from "../converse-autocomplete.js";
|
||||
import { CustomElement } from './element.js';
|
||||
import AutoComplete from './autocomplete.js';
|
||||
import { CustomElement } from 'components/element.js';
|
||||
import { FILTER_CONTAINS, FILTER_STARTSWITH } from './utils.js';
|
||||
import { api } from '@converse/headless/core';
|
||||
import { html } from 'lit-element';
|
||||
import { api } from "@converse/headless/core";
|
||||
|
||||
export default class AutoCompleteComponent extends CustomElement {
|
||||
|
||||
static get properties () {
|
||||
return {
|
||||
'getAutoCompleteList': { type: Function },
|
||||
@ -15,8 +15,8 @@ export default class AutoCompleteComponent extends CustomElement {
|
||||
'min_chars': { type: Number },
|
||||
'name': { type: String },
|
||||
'placeholder': { type: String },
|
||||
'triggers': { type: String },
|
||||
}
|
||||
'triggers': { type: String }
|
||||
};
|
||||
}
|
||||
|
||||
constructor () {
|
||||
@ -35,13 +35,21 @@ export default class AutoCompleteComponent extends CustomElement {
|
||||
return html`
|
||||
<div class="suggestion-box suggestion-box__name">
|
||||
<ul class="suggestion-box__results suggestion-box__results--above" hidden=""></ul>
|
||||
<input type="text" name="${this.name}"
|
||||
<input
|
||||
type="text"
|
||||
name="${this.name}"
|
||||
autocomplete="off"
|
||||
@keydown=${this.onKeyDown}
|
||||
@keyup=${this.onKeyUp}
|
||||
class="form-control suggestion-box__input"
|
||||
placeholder="${this.placeholder}"/>
|
||||
<span class="suggestion-box__additions visually-hidden" role="status" aria-live="assertive" aria-relevant="additions"></span>
|
||||
placeholder="${this.placeholder}"
|
||||
/>
|
||||
<span
|
||||
class="suggestion-box__additions visually-hidden"
|
||||
role="status"
|
||||
aria-live="assertive"
|
||||
aria-relevant="additions"
|
||||
></span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -56,9 +64,12 @@ export default class AutoCompleteComponent extends CustomElement {
|
||||
'list': () => this.getAutoCompleteList(),
|
||||
'match_current_word': true,
|
||||
'max_items': this.max_items,
|
||||
'min_chars': this.min_chars,
|
||||
'min_chars': this.min_chars
|
||||
});
|
||||
this.auto_complete.on('suggestion-box-selectcomplete', () => (this.auto_completing = false));
|
||||
this.auto_complete.on(
|
||||
'suggestion-box-selectcomplete',
|
||||
() => (this.auto_completing = false)
|
||||
);
|
||||
}
|
||||
|
||||
onKeyDown (ev) {
|
8
src/shared/autocomplete/index.js
Normal file
8
src/shared/autocomplete/index.js
Normal file
@ -0,0 +1,8 @@
|
||||
import './component.js';
|
||||
import AutoComplete from './autocomplete.js';
|
||||
import { FILTER_CONTAINS, FILTER_STARTSWITH } from './utils.js';
|
||||
import { _converse } from '@converse/headless/core';
|
||||
|
||||
_converse.FILTER_CONTAINS = FILTER_CONTAINS;
|
||||
_converse.FILTER_STARTSWITH = FILTER_STARTSWITH;
|
||||
_converse.AutoComplete = AutoComplete;
|
36
src/shared/autocomplete/suggestion.js
Normal file
36
src/shared/autocomplete/suggestion.js
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* An autocomplete suggestion
|
||||
*/
|
||||
class Suggestion extends String {
|
||||
/**
|
||||
* @param { Any } data - The auto-complete data. Ideally an object e.g. { label, value },
|
||||
* which specifies the value and human-presentable label of the suggestion.
|
||||
* @param { string } query - The query string being auto-completed
|
||||
*/
|
||||
constructor (data, query) {
|
||||
super();
|
||||
const o = Array.isArray(data)
|
||||
? { label: data[0], value: data[1] }
|
||||
: typeof data === 'object' && 'label' in data && 'value' in data
|
||||
? data
|
||||
: { label: data, value: data };
|
||||
|
||||
this.label = o.label || o.value;
|
||||
this.value = o.value;
|
||||
this.query = query;
|
||||
}
|
||||
|
||||
get lenth () {
|
||||
return this.label.length;
|
||||
}
|
||||
|
||||
toString () {
|
||||
return '' + this.label;
|
||||
}
|
||||
|
||||
valueOf () {
|
||||
return this.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export default Suggestion;
|
89
src/shared/autocomplete/utils.js
Normal file
89
src/shared/autocomplete/utils.js
Normal file
@ -0,0 +1,89 @@
|
||||
import { converse } from '@converse/headless/core';
|
||||
|
||||
const u = converse.env.utils;
|
||||
|
||||
export const helpers = {
|
||||
getElement (expr, el) {
|
||||
return typeof expr === 'string' ? (el || document).querySelector(expr) : expr || null;
|
||||
},
|
||||
|
||||
bind (element, o) {
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
if (!Object.prototype.hasOwnProperty.call(o, event)) {
|
||||
continue;
|
||||
}
|
||||
const callback = o[event];
|
||||
event.split(/\s+/).forEach(event => element.addEventListener(event, callback));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unbind (element, o) {
|
||||
if (element) {
|
||||
for (var event in o) {
|
||||
if (!Object.prototype.hasOwnProperty.call(o, event)) {
|
||||
continue;
|
||||
}
|
||||
const callback = o[event];
|
||||
event.split(/\s+/).forEach(event => element.removeEventListener(event, callback));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
regExpEscape (s) {
|
||||
return s.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
|
||||
},
|
||||
|
||||
isMention (word, ac_triggers) {
|
||||
return (
|
||||
ac_triggers.includes(word[0]) ||
|
||||
(u.isMentionBoundary(word[0]) && ac_triggers.includes(word[1]))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const FILTER_CONTAINS = function (text, input) {
|
||||
return RegExp(helpers.regExpEscape(input.trim()), 'i').test(text);
|
||||
};
|
||||
|
||||
export const FILTER_STARTSWITH = function (text, input) {
|
||||
return RegExp('^' + helpers.regExpEscape(input.trim()), 'i').test(text);
|
||||
};
|
||||
|
||||
const SORT_BY_LENGTH = function (a, b) {
|
||||
if (a.length !== b.length) {
|
||||
return a.length - b.length;
|
||||
}
|
||||
return a < b ? -1 : 1;
|
||||
};
|
||||
|
||||
export const SORT_BY_QUERY_POSITION = function (a, b) {
|
||||
const query = a.query.toLowerCase();
|
||||
const x = a.label.toLowerCase().indexOf(query);
|
||||
const y = b.label.toLowerCase().indexOf(query);
|
||||
|
||||
if (x === y) {
|
||||
return SORT_BY_LENGTH(a, b);
|
||||
}
|
||||
return (x === -1 ? Infinity : x) < (y === -1 ? Infinity : y) ? -1 : 1;
|
||||
};
|
||||
|
||||
export const ITEM = (text, input) => {
|
||||
input = input.trim();
|
||||
const element = document.createElement('li');
|
||||
element.setAttribute('aria-selected', 'false');
|
||||
|
||||
const regex = new RegExp('(' + input + ')', 'ig');
|
||||
const parts = input ? text.split(regex) : [text];
|
||||
parts.forEach(txt => {
|
||||
if (input && txt.match(regex)) {
|
||||
const match = document.createElement('mark');
|
||||
match.textContent = txt;
|
||||
element.appendChild(match);
|
||||
} else {
|
||||
element.appendChild(document.createTextNode(txt));
|
||||
}
|
||||
});
|
||||
return element;
|
||||
};
|
Loading…
Reference in New Issue
Block a user