Move the autocomplete code into ./shared

And remove it as a plugin.
This commit is contained in:
JC Brand 2020-12-10 11:08:17 +01:00
parent 9174be8ff3
commit bb3f52d2f2
11 changed files with 171 additions and 157 deletions

View File

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

View File

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

View File

@ -14,15 +14,14 @@ 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/chatview/index.js"; // Renders standalone chat boxes for single user chat
import "./plugins/controlbox/index.js"; // The control box
import "./plugins/dragresize.js"; // Allows chat boxes to be resized by dragging them
import "./plugins/fullscreen.js";
import "./plugins/mam-views.js";
import "./plugins/minimize.js"; // Allows chat boxes to be minimized
import "./plugins/muc-views/index.js"; // Views related to MUC
import "./plugins/muc-views/index.js"; // Views related to MUC
import "./plugins/headlines-view/index.js";
import "./plugins/notifications.js";
import "./plugins/omemo.js";
@ -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',

View File

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

View File

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

View File

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

View File

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

View File

@ -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}"
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>
<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>
</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) {

View 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;

View 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;

View 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;
};