Let bookmarks be created/removed via a modal

This commit is contained in:
JC Brand 2022-02-05 22:47:05 +01:00
parent 9d250c3cbf
commit 505416a59e
12 changed files with 95 additions and 48 deletions

View File

@ -14,12 +14,14 @@ class MUCBookmarkForm extends CustomElement {
connectedCallback () { connectedCallback () {
super.connectedCallback(); super.connectedCallback();
this.model = _converse.chatboxes.get(this.jid); this.model = _converse.chatboxes.get(this.jid);
this.bookmark = _converse.bookmarks.findWhere({ 'jid': this.model.get('jid') });
} }
render () { render () {
return tpl_muc_bookmark_form( return tpl_muc_bookmark_form(
Object.assign(this.model.toJSON(), { Object.assign(this.model.toJSON(), {
'onCancel': ev => this.closeBookmarkForm(ev), 'bookmark': this.bookmark,
'onCancel': ev => this.removeBookmark(ev),
'onSubmit': ev => this.onBookmarkFormSubmitted(ev) 'onSubmit': ev => this.onBookmarkFormSubmitted(ev)
}) })
); );
@ -36,9 +38,16 @@ class MUCBookmarkForm extends CustomElement {
this.closeBookmarkForm(ev); this.closeBookmarkForm(ev);
} }
removeBookmark (ev) {
this.bookmark?.destroy();
this.closeBookmarkForm(ev);
}
closeBookmarkForm (ev) { closeBookmarkForm (ev) {
ev.preventDefault(); ev.preventDefault();
this.model.session.save('view', null); const evt = document.createEvent('Event');
evt.initEvent('hide.bs.modal', true, true);
this.dispatchEvent(evt);
} }
} }

View File

@ -1,4 +1,5 @@
import { _converse, converse } from '@converse/headless/core'; import MUCBookmarkFormModal from './modal.js';
import { _converse, api, converse } from '@converse/headless/core';
const { u } = converse.env; const { u } = converse.env;
@ -30,13 +31,9 @@ export const bookmarkableChatRoomView = {
u.showElement(this.bookmark_form.el); u.showElement(this.bookmark_form.el);
}, },
toggleBookmark (ev) { showBookmarkModal(ev) {
ev?.preventDefault(); ev?.preventDefault();
const models = _converse.bookmarks.where({ 'jid': this.model.get('jid') }); const jid = this.model.get('jid');
if (!models.length) { api.modal.show(MUCBookmarkFormModal, { jid }, ev);
this.model.session.set('view', converse.MUC.VIEWS.BOOKMARK);
} else {
models.forEach(model => model.destroy());
}
} }
}; };

View File

@ -0,0 +1,19 @@
import './form.js';
import BaseModal from "plugins/modal/base.js";
import tpl_modal from './templates/modal.js';
const MUCBookmarkFormModal = BaseModal.extend({
id: "converse-bookmark-modal",
initialize (attrs) {
this.jid = attrs.jid;
this.affiliation = attrs.affiliation;
BaseModal.prototype.initialize.apply(this, arguments);
},
toHTML () {
return tpl_modal(this);
}
});
export default MUCBookmarkFormModal;

View File

@ -3,22 +3,24 @@ import { __ } from 'i18n';
export default (o) => { export default (o) => {
const i18n_heading = __('Bookmark this groupchat'); const name = o.bookmark?.get('name') ?? o.name;
const nick = o.bookmark?.get('nick') ?? o.nick;
const i18n_heading = __('Bookmark for "%1$s"', name);
const i18n_autojoin = __('Would you like this groupchat to be automatically joined upon startup?'); const i18n_autojoin = __('Would you like this groupchat to be automatically joined upon startup?');
const i18n_cancel = __('Cancel'); const i18n_remove = __('Remove');
const i18n_name = __('The name for this bookmark:'); const i18n_name = __('The name for this bookmark:');
const i18n_nick = __('What should your nickname for this groupchat be?'); const i18n_nick = __('What should your nickname for this groupchat be?');
const i18n_submit = __('Save'); const i18n_submit = o.bookmark ? __('Update') : __('Save');
return html` return html`
<form class="converse-form chatroom-form" @submit=${o.onSubmit}> <form class="converse-form chatroom-form" @submit=${o.onSubmit}>
<legend>${i18n_heading}</legend> <legend>${i18n_heading}</legend>
<fieldset class="form-group"> <fieldset class="form-group">
<label for="converse_muc_bookmark_name">${i18n_name}</label> <label for="converse_muc_bookmark_name">${i18n_name}</label>
<input class="form-control" type="text" value="${o.name}" name="name" required="required" id="converse_muc_bookmark_name"/> <input class="form-control" type="text" value="${name}" name="name" required="required" id="converse_muc_bookmark_name"/>
</fieldset> </fieldset>
<fieldset class="form-group"> <fieldset class="form-group">
<label for="converse_muc_bookmark_nick">${i18n_nick}</label> <label for="converse_muc_bookmark_nick">${i18n_nick}</label>
<input class="form-control" type="text" name="nick" value="${o.nick || ''}" id="converse_muc_bookmark_nick"/> <input class="form-control" type="text" name="nick" value="${nick || ''}" id="converse_muc_bookmark_nick"/>
</fieldset> </fieldset>
<fieldset class="form-group form-check"> <fieldset class="form-group form-check">
<input class="form-check-input" id="converse_muc_bookmark_autojoin" type="checkbox" name="autojoin"/> <input class="form-check-input" id="converse_muc_bookmark_autojoin" type="checkbox" name="autojoin"/>
@ -26,7 +28,7 @@ export default (o) => {
</fieldset> </fieldset>
<fieldset class="form-group"> <fieldset class="form-group">
<input class="btn btn-primary" type="submit" value="${i18n_submit}"> <input class="btn btn-primary" type="submit" value="${i18n_submit}">
<input class="btn btn-secondary button-cancel" type="button" value="${i18n_cancel}" @click=${o.onCancel}> ${o.bookmark ? html`<input class="btn btn-secondary button-remove" type="button" value="${i18n_remove}" @click=${o.onCancel}>` : '' }
</fieldset> </fieldset>
</form> </form>
`; `;

View File

@ -0,0 +1,19 @@
import { __ } from 'i18n';
import { html } from "lit";
import { modal_header_close_button } from "plugins/modal/templates/buttons.js"
export default (o) => {
const i18n_moderator_tools = __('Bookmark');
return html`
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="converse-modtools-modal-label">${i18n_moderator_tools}</h5>
${modal_header_close_button}
</div>
<div class="modal-body d-flex flex-column">
<converse-muc-bookmark-form class="muc-form-container" jid="${o.jid}"></converse-muc-bookmark-form>
</div>
</div>
</div>`;
}

View File

@ -5,8 +5,7 @@ const { Strophe, u, sizzle, $iq } = converse.env;
describe("A chat room", function () { describe("A chat room", function () {
it("can be bookmarked", mock.initConverse( it("can be bookmarked", mock.initConverse(['chatBoxesFetched'], {}, async (_converse) => {
['chatBoxesFetched'], {}, async function (_converse) {
await mock.waitForRoster(_converse, 'current', 0); await mock.waitForRoster(_converse, 'current', 0);
await mock.waitUntilDiscoConfirmed( await mock.waitUntilDiscoConfirmed(
@ -27,20 +26,13 @@ describe("A chat room", function () {
await mock.returnMemberLists(_converse, muc_jid, [], ['member', 'admin', 'owner']); await mock.returnMemberLists(_converse, muc_jid, [], ['member', 'admin', 'owner']);
await u.waitUntil(() => view.querySelector('.toggle-bookmark') !== null); await u.waitUntil(() => view.querySelector('.toggle-bookmark') !== null);
const toggle = view.querySelector('.toggle-bookmark'); const toggle = view.querySelector('.toggle-bookmark');
expect(toggle.title).toBe('Bookmark this groupchat'); expect(toggle.title).toBe('Bookmark this groupchat');
toggle.click(); toggle.click();
const cancel_button = await u.waitUntil(() => view.querySelector('.button-cancel')); const modal = _converse.api.modal.get('converse-bookmark-modal');
expect(view.model.session.get('view')).toBe('bookmark-form'); await u.waitUntil(() => u.isVisible(modal.el), 1000);
cancel_button.click();
await u.waitUntil(() => view.model.session.get('view') === null);
expect(u.hasClass('on-button', toggle), false);
expect(toggle.title).toBe('Bookmark this groupchat');
toggle.click();
/* Client uploads data: /* Client uploads data:
* -------------------- * --------------------
@ -74,13 +66,13 @@ describe("A chat room", function () {
* </iq> * </iq>
*/ */
expect(view.model.get('bookmarked')).toBeFalsy(); expect(view.model.get('bookmarked')).toBeFalsy();
const form = await u.waitUntil(() => view.querySelector('.chatroom-form')); const form = await u.waitUntil(() => modal.el.querySelector('.chatroom-form'));
form.querySelector('input[name="name"]').value = 'Play&apos;s the Thing'; form.querySelector('input[name="name"]').value = 'Play&apos;s the Thing';
form.querySelector('input[name="autojoin"]').checked = 'checked'; form.querySelector('input[name="autojoin"]').checked = 'checked';
form.querySelector('input[name="nick"]').value = 'JC'; form.querySelector('input[name="nick"]').value = 'JC';
const IQ_stanzas = _converse.connection.IQ_stanzas; const IQ_stanzas = _converse.connection.IQ_stanzas;
view.querySelector('converse-muc-bookmark-form .btn-primary').click(); modal.el.querySelector('converse-muc-bookmark-form .btn-primary').click();
const sent_stanza = await u.waitUntil( const sent_stanza = await u.waitUntil(
() => IQ_stanzas.filter(s => sizzle('iq publish[node="storage:bookmarks"]', s).length).pop()); () => IQ_stanzas.filter(s => sizzle('iq publish[node="storage:bookmarks"]', s).length).pop());
@ -227,7 +219,6 @@ describe("A chat room", function () {
})); }));
it("can be unbookmarked", mock.initConverse([], {}, async function (_converse) { it("can be unbookmarked", mock.initConverse([], {}, async function (_converse) {
const { u, Strophe } = converse.env; const { u, Strophe } = converse.env;
await mock.waitForRoster(_converse, 'current', 0); await mock.waitForRoster(_converse, 'current', 0);
await mock.waitUntilBookmarksReturned(_converse); await mock.waitUntilBookmarksReturned(_converse);
@ -240,14 +231,14 @@ describe("A chat room", function () {
const view = _converse.chatboxviews.get(muc_jid); const view = _converse.chatboxviews.get(muc_jid);
await u.waitUntil(() => view.querySelector('.toggle-bookmark')); await u.waitUntil(() => view.querySelector('.toggle-bookmark'));
spyOn(view, 'toggleBookmark').and.callThrough(); spyOn(view, 'showBookmarkModal').and.callThrough();
spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough(); spyOn(_converse.bookmarks, 'sendBookmarkStanza').and.callThrough();
_converse.bookmarks.create({ _converse.bookmarks.create({
'jid': view.model.get('jid'), 'jid': view.model.get('jid'),
'autojoin': false, 'autojoin': false,
'name': 'The Play', 'name': 'The Play',
'nick': ' Othello' 'nick': 'Othello'
}); });
expect(_converse.bookmarks.length).toBe(1); expect(_converse.bookmarks.length).toBe(1);
@ -257,7 +248,19 @@ describe("A chat room", function () {
spyOn(_converse.connection, 'getUniqueId').and.callThrough(); spyOn(_converse.connection, 'getUniqueId').and.callThrough();
const bookmark_icon = view.querySelector('.toggle-bookmark'); const bookmark_icon = view.querySelector('.toggle-bookmark');
bookmark_icon.click(); bookmark_icon.click();
expect(view.toggleBookmark).toHaveBeenCalled(); expect(view.showBookmarkModal).toHaveBeenCalled();
const modal = _converse.api.modal.get('converse-bookmark-modal');
await u.waitUntil(() => u.isVisible(modal.el), 1000);
const form = await u.waitUntil(() => modal.el.querySelector('.chatroom-form'));
expect(form.querySelector('input[name="name"]').value).toBe('The Play');
expect(form.querySelector('input[name="autojoin"]').checked).toBeFalsy();
expect(form.querySelector('input[name="nick"]').value).toBe('Othello');
// Remove the bookmark
modal.el.querySelector('.button-remove').click();
await u.waitUntil(() => view.querySelector('.chatbox-title__text .fa-bookmark') === null); await u.waitUntil(() => view.querySelector('.chatbox-title__text .fa-bookmark') === null);
expect(_converse.bookmarks.length).toBe(0); expect(_converse.bookmarks.length).toBe(0);

View File

@ -1,8 +1,9 @@
import { checkBookmarksSupport } from '@converse/headless/plugins/bookmarks/utils'; import MUCBookmarkFormModal from './modal.js';
import invokeMap from 'lodash-es/invokeMap'; import invokeMap from 'lodash-es/invokeMap';
import { Model } from '@converse/skeletor/src/model.js'; import { Model } from '@converse/skeletor/src/model.js';
import { _converse, api, converse } from '@converse/headless/core';
import { __ } from 'i18n'; import { __ } from 'i18n';
import { _converse, api, converse } from '@converse/headless/core';
import { checkBookmarksSupport } from '@converse/headless/plugins/bookmarks/utils';
export function getHeadingButtons (view, buttons) { export function getHeadingButtons (view, buttons) {
@ -11,7 +12,7 @@ export function getHeadingButtons (view, buttons) {
const data = { const data = {
'i18n_title': bookmarked ? __('Unbookmark this groupchat') : __('Bookmark this groupchat'), 'i18n_title': bookmarked ? __('Unbookmark this groupchat') : __('Bookmark this groupchat'),
'i18n_text': bookmarked ? __('Unbookmark') : __('Bookmark'), 'i18n_text': bookmarked ? __('Unbookmark') : __('Bookmark'),
'handler': ev => view.toggleBookmark(ev), 'handler': ev => view.showBookmarkModal(ev),
'a_class': 'toggle-bookmark', 'a_class': 'toggle-bookmark',
'icon_class': 'fa-bookmark', 'icon_class': 'fa-bookmark',
'name': 'bookmark' 'name': 'bookmark'
@ -33,11 +34,10 @@ export function removeBookmarkViaEvent (ev) {
} }
} }
export async function addBookmarkViaEvent (ev) { export function addBookmarkViaEvent (ev) {
ev.preventDefault(); ev.preventDefault();
const jid = ev.target.getAttribute('data-room-jid'); const jid = ev.target.getAttribute('data-room-jid');
const room = await api.rooms.open(jid, { 'bring_to_foreground': true }); api.modal.show(MUCBookmarkFormModal, { jid }, ev);
room.session.save('view', converse.MUC.VIEWS.BOOKMARK);
} }

View File

@ -14,7 +14,6 @@ import './styles/index.scss';
converse.MUC.VIEWS = { converse.MUC.VIEWS = {
CONFIG: 'config-form', CONFIG: 'config-form',
BOOKMARK: 'bookmark-form'
} }
converse.plugins.add('converse-muc-views', { converse.plugins.add('converse-muc-views', {

View File

@ -1,15 +1,15 @@
import '../modtools.js'; import '../modtools.js';
import BootstrapModal from "plugins/modal/base.js"; import BaseModal from "plugins/modal/base.js";
import tpl_moderator_tools from './templates/moderator-tools.js'; import tpl_moderator_tools from './templates/moderator-tools.js';
const ModeratorToolsModal = BootstrapModal.extend({ const ModeratorToolsModal = BaseModal.extend({
id: "converse-modtools-modal", id: "converse-modtools-modal",
persistent: true, persistent: true,
initialize (attrs) { initialize (attrs) {
this.jid = attrs.jid; this.jid = attrs.jid;
this.affiliation = attrs.affiliation; this.affiliation = attrs.affiliation;
BootstrapModal.prototype.initialize.apply(this, arguments); BaseModal.prototype.initialize.apply(this, arguments);
}, },
toHTML () { toHTML () {

View File

@ -124,8 +124,6 @@ export function getChatRoomBodyTemplate (o) {
if (view === converse.MUC.VIEWS.CONFIG) { if (view === converse.MUC.VIEWS.CONFIG) {
return html`<converse-muc-config-form class="muc-form-container" jid="${jid}"></converse-muc-config-form>`; return html`<converse-muc-config-form class="muc-form-container" jid="${jid}"></converse-muc-config-form>`;
} else if (view === converse.MUC.VIEWS.BOOKMARK) {
return html`<converse-muc-bookmark-form class="muc-form-container" jid="${jid}"></converse-muc-bookmark-form>`;
} else { } else {
return html` return html`
${ conn_status == RS.PASSWORD_REQUIRED ? html`<converse-muc-password-form class="muc-form-container" jid="${jid}"></converse-muc-password-form>` : '' } ${ conn_status == RS.PASSWORD_REQUIRED ? html`<converse-muc-password-form class="muc-form-container" jid="${jid}"></converse-muc-password-form>` : '' }

View File

@ -38,7 +38,8 @@
muc_domain: 'conference.chat.example.org', muc_domain: 'conference.chat.example.org',
muc_respect_autojoin: true, muc_respect_autojoin: true,
view_mode: 'fullscreen', view_mode: 'fullscreen',
websocket_url: 'ws://chat.example.org:5380/xmpp-websocket', // websocket_url: 'ws://chat.example.org:5380/xmpp-websocket',
websocket_url: 'wss://conversejs.org/xmpp-websocket',
// bosh_service_url: 'http://chat.example.org:5280/http-bind', // bosh_service_url: 'http://chat.example.org:5280/http-bind',
allow_user_defined_connection_url: true, allow_user_defined_connection_url: true,
muc_show_logs_before_join: true, muc_show_logs_before_join: true,

View File

@ -9,7 +9,7 @@ module.exports = merge(common, {
devtool: "inline-source-map", devtool: "inline-source-map",
devServer: { devServer: {
static: [ path.resolve(__dirname, '../') ], static: [ path.resolve(__dirname, '../') ],
port: 3003 port: 3004
}, },
plugins: [ plugins: [
new HTMLWebpackPlugin({ new HTMLWebpackPlugin({