Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/jsxc/jsxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsualko <klaus@jsxc.org>2021-12-31 02:22:46 +0300
committersualko <klaus@jsxc.org>2021-12-31 02:30:45 +0300
commit49e1ad7c81d76cf3af54ae9ddb1c9e08b4bae5a0 (patch)
treebcb8f7649193412079698c0a3dc8608faad166b7
parentb49f4deae29a4a3e4c0a83c9a7733286ce10e1b5 (diff)
feat: add XEP-0308 as text command
Last Message Correction
-rw-r--r--scss/partials/_window.scss7
-rw-r--r--src/Account.ts2
-rw-r--r--src/CommandRepository.ts8
-rw-r--r--src/Contact.ts3
-rw-r--r--src/Message.interface.ts14
-rw-r--r--src/Message.ts50
-rw-r--r--src/Transcript.ts26
-rw-r--r--src/bootstrap/plugins.ts4
-rw-r--r--src/connection/xmpp/MessageElement.ts2
-rw-r--r--src/connection/xmpp/handlers/chatMessage.ts4
-rw-r--r--src/plugins/LastMessageCorrectionPlugin.ts151
-rw-r--r--src/ui/ChatWindow.ts19
-rw-r--r--src/ui/ChatWindowMessage.ts43
-rw-r--r--template/chat-window-message.hbs1
14 files changed, 312 insertions, 22 deletions
diff --git a/scss/partials/_window.scss b/scss/partials/_window.scss
index 7eea2cb5..8dd31dc6 100644
--- a/scss/partials/_window.scss
+++ b/scss/partials/_window.scss
@@ -526,6 +526,13 @@
}
}
+.jsxc-chatmessage.jsxc-edited {
+ .jsxc-version::before {
+ @extend %jsxc-icon-font;
+ content: map-get($jsxc-icons-map, "edit");
+ }
+}
+
.jsxc-mark::before {
height: 1em;
diff --git a/src/Account.ts b/src/Account.ts
index 8ebacf5b..6a007ec9 100644
--- a/src/Account.ts
+++ b/src/Account.ts
@@ -51,7 +51,7 @@ export default class Account {
private ownDiscoInfo: DiscoInfoChangeable;
- private hookRepository = new HookRepository<any>();
+ private hookRepository = new HookRepository();
private contactManager: ContactManager;
diff --git a/src/CommandRepository.ts b/src/CommandRepository.ts
index 90148d7d..97c3753b 100644
--- a/src/CommandRepository.ts
+++ b/src/CommandRepository.ts
@@ -13,7 +13,11 @@ export class ArgumentError extends Error {
}
}
-export type CommandAction = (args: string[], contact: IContact | MultiUserContact) => Promise<boolean>;
+export type CommandAction = (
+ args: string[],
+ contact: IContact | MultiUserContact,
+ message?: string
+) => Promise<boolean>;
export default class CommandRepository {
private commands: {
@@ -47,7 +51,7 @@ export default class CommandRepository {
return Promise.resolve(false);
}
- return this.commands[command].action(args, contact);
+ return this.commands[command].action(args, contact, message);
}
public getHelp() {
diff --git a/src/Contact.ts b/src/Contact.ts
index 12a415d8..a77d5b64 100644
--- a/src/Contact.ts
+++ b/src/Contact.ts
@@ -15,6 +15,7 @@ import ChatWindow from './ui/ChatWindow';
import ContactProvider from './ContactProvider';
import DiscoInfo from './DiscoInfo';
import { IJID } from './JID.interface';
+import { IMessage } from './Message.interface';
export default class Contact implements IIdentifiable, IContact {
protected storage: Storage;
@@ -111,7 +112,7 @@ export default class Contact implements IIdentifiable, IContact {
return this.account;
}
- public addSystemMessage(messageString: string): Message {
+ public addSystemMessage(messageString: string): IMessage {
let message = new Message({
peer: this.getJid(),
direction: Message.DIRECTION.SYS,
diff --git a/src/Message.interface.ts b/src/Message.interface.ts
index 2a44ece8..ef5e7483 100644
--- a/src/Message.interface.ts
+++ b/src/Message.interface.ts
@@ -43,6 +43,8 @@ export interface IMessagePayload {
chatMarkersReceived?: boolean;
chatMarkersDisplayed?: boolean;
chatMarkersAcknowledged?: boolean;
+ replacedBy?: string;
+ original?: string;
}
export interface IMessage {
@@ -145,4 +147,16 @@ export interface IMessage {
getErrorMessage(): string;
updateProgress(transferred: number, complete: number);
+
+ getLastVersion(): IMessage;
+
+ getReplacedBy(): IMessage;
+
+ setReplacedBy(message: IMessage): void;
+
+ getOriginal(): IMessage;
+
+ setOriginal(message: IMessage): void;
+
+ isReplacement(): boolean;
}
diff --git a/src/Message.ts b/src/Message.ts
index a66272d9..e3ab0d7d 100644
--- a/src/Message.ts
+++ b/src/Message.ts
@@ -54,6 +54,10 @@ export default class Message implements IIdentifiable, IMessage {
private attachment: Attachment;
+ private replacedBy: IMessage;
+
+ private original: IMessage;
+
public static readonly DIRECTION = DIRECTION;
public static readonly MSGTYPE = ContactType;
@@ -367,6 +371,52 @@ export default class Message implements IIdentifiable, IMessage {
public updateProgress(transferred: number, size: number) {
this.data.set('progress', transferred / size);
}
+
+ public getLastVersion(): IMessage {
+ let replacedBy = this.getReplacedBy();
+
+ while (replacedBy && replacedBy.getReplacedBy()) {
+ replacedBy = replacedBy.getReplacedBy();
+ }
+
+ return replacedBy || this;
+ }
+
+ public getReplacedBy(): IMessage {
+ if (this.replacedBy) {
+ return this.replacedBy;
+ }
+
+ const replacedByUid = this.data.get('replacedBy');
+
+ this.replacedBy = replacedByUid ? new Message(replacedByUid) : undefined;
+
+ return this.replacedBy;
+ }
+
+ public setReplacedBy(message: IMessage): void {
+ this.data.set('replacedBy', message.getUid());
+ }
+
+ public getOriginal(): IMessage {
+ if (this.original) {
+ return this.original;
+ }
+
+ const originalUid = this.data.get('original');
+
+ this.original = originalUid ? new Message(originalUid) : undefined;
+
+ return this.original;
+ }
+
+ public setOriginal(message: IMessage): void {
+ this.data.set('original', message.getUid());
+ }
+
+ public isReplacement(): boolean {
+ return !!this.data.get('original');
+ }
}
function convertUrlToLink(text: string) {
diff --git a/src/Transcript.ts b/src/Transcript.ts
index dcb9d883..cf00f29d 100644
--- a/src/Transcript.ts
+++ b/src/Transcript.ts
@@ -55,7 +55,23 @@ export default class Transcript {
public getFirstChatMessage(): IMessage {
for (let message of this.getGenerator()) {
- if (!message.isSystem()) {
+ if (!message.isSystem() && !message.isReplacement()) {
+ return message;
+ }
+ }
+ }
+
+ public getFirstIncomingMessage(): IMessage {
+ for (let message of this.getGenerator()) {
+ if (message.isIncoming() && !message.isReplacement()) {
+ return message;
+ }
+ }
+ }
+
+ public getFirstOutgoingMessage(): IMessage {
+ for (let message of this.getGenerator()) {
+ if (message.isOutgoing() && !message.isReplacement()) {
return message;
}
}
@@ -69,6 +85,14 @@ export default class Transcript {
return this.firstMessage;
}
+ public getFirstOriginalMessage(): IMessage {
+ for (let message of this.getGenerator()) {
+ if (!message.isReplacement()) {
+ return message;
+ }
+ }
+ }
+
public getLastMessage(): IMessage {
if (this.lastMessage) {
return this.lastMessage;
diff --git a/src/bootstrap/plugins.ts b/src/bootstrap/plugins.ts
index 933ef013..2ad52c77 100644
--- a/src/bootstrap/plugins.ts
+++ b/src/bootstrap/plugins.ts
@@ -17,7 +17,8 @@ import CommandPlugin from '@src/plugins/CommandPlugin';
import VersionPlugin from '@src/plugins/VersionPlugin';
import TimePlugin from '../plugins/TimePlugin';
import JingleMessageInitiationPlugin from '../plugins/JingleMessageInitiationPlugin';
-import AvatarPEPPlugin from '../plugins/AvatarPEPPlugin';
+import AvatarPEPPlugin from '@src/plugins/AvatarPEPPlugin';
+import LastMessageCorrectionPlugin from '@src/plugins/LastMessageCorrectionPlugin';
Client.addPlugin(OTRPlugin);
Client.addPlugin(OMEMOPlugin);
@@ -38,3 +39,4 @@ Client.addPlugin(VersionPlugin);
Client.addPlugin(TimePlugin);
Client.addPlugin(JingleMessageInitiationPlugin);
Client.addPlugin(AvatarPEPPlugin);
+Client.addPlugin(LastMessageCorrectionPlugin);
diff --git a/src/connection/xmpp/MessageElement.ts b/src/connection/xmpp/MessageElement.ts
index a4cd902c..933e37a7 100644
--- a/src/connection/xmpp/MessageElement.ts
+++ b/src/connection/xmpp/MessageElement.ts
@@ -71,7 +71,7 @@ export class MessageElement {
return this.element.find(selector);
}
- public get(index?) {
+ public get(index?: number) {
return this.element.get(index);
}
diff --git a/src/connection/xmpp/handlers/chatMessage.ts b/src/connection/xmpp/handlers/chatMessage.ts
index 6b3b670b..d95fdec0 100644
--- a/src/connection/xmpp/handlers/chatMessage.ts
+++ b/src/connection/xmpp/handlers/chatMessage.ts
@@ -51,6 +51,10 @@ export default class extends AbstractHandler {
let pipe = this.account.getPipe('afterReceiveMessage');
pipe.run(peerContact, message, messageElement.get(0)).then(([contact, message]) => {
+ if (!message) {
+ return;
+ }
+
if (message.getPlaintextMessage() || message.getHtmlMessage() || message.hasAttachment()) {
contact.getTranscript().pushMessage(message);
} else {
diff --git a/src/plugins/LastMessageCorrectionPlugin.ts b/src/plugins/LastMessageCorrectionPlugin.ts
new file mode 100644
index 00000000..8aa1d013
--- /dev/null
+++ b/src/plugins/LastMessageCorrectionPlugin.ts
@@ -0,0 +1,151 @@
+import { AbstractPlugin, IMetaData } from '../plugin/AbstractPlugin';
+import PluginAPI from '../plugin/PluginAPI';
+import Translation from '@util/Translation';
+import * as Namespace from '@connection/xmpp/namespace';
+import { IContact } from '@src/Contact.interface';
+import { IMessage } from '@src/Message.interface';
+import Message from '@src/Message';
+
+/**
+ * XEP-0308: Last Message Correction
+ *
+ * @version: 1.2.0
+ * @see: https://xmpp.org/extensions/xep-0308.html
+ *
+ */
+
+const CORRECTION_CMD = '/fix';
+const LMC = 'urn:xmpp:message-correct:0';
+
+const MIN_VERSION = '4.3.0';
+const MAX_VERSION = '99.0.0';
+
+Namespace.register('LAST_MSG_CORRECTION', LMC);
+
+export default class LastMessageCorrectionPlugin extends AbstractPlugin {
+ public static getId(): string {
+ return 'lmc';
+ }
+
+ public static getName(): string {
+ return 'Last Message Correction';
+ }
+
+ public static getMetaData(): IMetaData {
+ return {
+ description: Translation.t('setting-lmc-enable'),
+ xeps: [
+ {
+ id: 'XEP-0308',
+ name: 'Last Message Correction',
+ version: '1.2.0',
+ },
+ ],
+ };
+ }
+
+ private correctionRequests: { [contactUid: string]: IMessage } = {};
+
+ constructor(pluginAPI: PluginAPI) {
+ super(MIN_VERSION, MAX_VERSION, pluginAPI);
+
+ pluginAPI.addAfterReceiveMessageProcessor(this.checkMessageCorrection, 90);
+
+ pluginAPI.registerCommand(
+ CORRECTION_CMD,
+ async (args, contact, messageString) => {
+ const originalMessage = contact.getTranscript().getFirstOutgoingMessage();
+
+ this.correctionRequests[contact.getUid()] = originalMessage;
+
+ if (!originalMessage) {
+ return false;
+ }
+
+ const chatWindow = contact.getChatWindow();
+ const message = new Message({
+ peer: contact.getJid(),
+ direction: Message.DIRECTION.OUT,
+ type: contact.getType(),
+ plaintextMessage: messageString.replace(/^\/fix /, ''),
+ attachment: chatWindow.getAttachment(),
+ unread: false,
+ original: originalMessage.getUid(),
+ });
+
+ contact.getTranscript().pushMessage(message);
+
+ chatWindow.clearAttachment();
+
+ let pipe = contact.getAccount().getPipe('preSendMessage');
+
+ return pipe
+ .run(contact, message)
+ .then(([contact, message]) => {
+ originalMessage.getLastVersion().setReplacedBy(message);
+
+ contact.getAccount().getConnection().sendMessage(message);
+
+ return true;
+ })
+ .catch(err => {
+ this.pluginAPI.Log.warn('Error during preSendMessage pipe', err);
+
+ return false;
+ });
+ },
+ 'cmd_correction'
+ );
+
+ pluginAPI.addPreSendMessageStanzaProcessor(async (message, xmlMsg) => {
+ const contact = this.pluginAPI.getContact(message.getPeer());
+ const originalMessage = this.correctionRequests[contact.getUid()];
+
+ if (!originalMessage || originalMessage.getLastVersion().getUid() !== message.getUid()) {
+ return [message, xmlMsg];
+ }
+
+ xmlMsg
+ .c('replace', {
+ xmlns: LMC,
+ id: originalMessage.getAttrId(),
+ })
+ .up();
+
+ return [message, xmlMsg];
+ }, 90);
+ }
+
+ // review carbon copy
+ private checkMessageCorrection = async (
+ contact: IContact,
+ message: IMessage,
+ stanza: Element
+ ): Promise<[IContact, IMessage, Element]> => {
+ const replaceElement = $(stanza).find(`>replace[xmlns="${LMC}"]`);
+
+ if (replaceElement.length === 0) {
+ return [contact, message, stanza];
+ }
+
+ const attrIdToBeReplaced = replaceElement.attr('id');
+
+ if (!attrIdToBeReplaced) {
+ return [contact, message, stanza];
+ }
+
+ const firstIncomingMessage = contact.getTranscript().getFirstIncomingMessage();
+
+ if (firstIncomingMessage && firstIncomingMessage.getAttrId() === attrIdToBeReplaced) {
+ message.setOriginal(firstIncomingMessage);
+
+ contact.getTranscript().pushMessage(message);
+
+ firstIncomingMessage.getLastVersion().setReplacedBy(message);
+
+ return [contact, undefined, stanza];
+ }
+
+ return [contact, message, stanza];
+ };
+}
diff --git a/src/ui/ChatWindow.ts b/src/ui/ChatWindow.ts
index ce03e97e..ba25c670 100644
--- a/src/ui/ChatWindow.ts
+++ b/src/ui/ChatWindow.ts
@@ -183,6 +183,15 @@ export default class ChatWindow {
this.element.find('.jsxc-bar__caption__secondary').text(text);
}
+ public getInput(): string {
+ return this.inputElement.val().toString();
+ }
+
+ public setInput(text: string) {
+ this.inputElement.val(text);
+ this.inputElement.trigger('focus');
+ }
+
public appendTextToInput(text: string = '') {
let value = this.inputElement.val();
@@ -228,6 +237,10 @@ export default class ChatWindow {
return this.settingsMenu.addEntry(label, cb, className);
}
+ public getAttachment(): Attachment {
+ return this.attachmentDeposition;
+ }
+
public setAttachment(attachment: Attachment) {
this.attachmentDeposition = attachment;
@@ -627,7 +640,7 @@ export default class ChatWindow {
}
private restoreLocalHistory() {
- let firstMessage = this.getTranscript().getFirstMessage();
+ let firstMessage = this.getTranscript().getFirstOriginalMessage();
if (!firstMessage) {
return;
@@ -668,7 +681,9 @@ export default class ChatWindow {
let message = this.getTranscript().getMessage(firstMessageId);
- this.postMessage(message);
+ if (!message.isReplacement()) {
+ this.postMessage(message);
+ }
});
}
diff --git a/src/ui/ChatWindowMessage.ts b/src/ui/ChatWindowMessage.ts
index 3e646a3a..8547fc5f 100644
--- a/src/ui/ChatWindowMessage.ts
+++ b/src/ui/ChatWindowMessage.ts
@@ -10,9 +10,13 @@ import Translation from '@util/Translation';
let chatWindowMessageTemplate = require('../../template/chat-window-message.hbs');
export default class ChatWindowMessage {
- private element;
+ private element: JQuery<HTMLElement>;
+
+ private message: IMessage;
+
+ constructor(private originalMessage: IMessage, private chatWindow: ChatWindow) {
+ this.message = originalMessage.getLastVersion();
- constructor(private message: IMessage, private chatWindow: ChatWindow) {
this.generateElement();
this.registerHooks();
}
@@ -36,20 +40,22 @@ export default class ChatWindowMessage {
}
private getNextMessage() {
- let nextId = this.message.getNextId();
+ let nextId = this.originalMessage.getNextId();
- if (!nextId) {
- return;
- }
+ while (nextId) {
+ let nextMessage = this.chatWindow.getTranscript().getMessage(nextId);
- let nextMessage = this.chatWindow.getTranscript().getMessage(nextId);
+ if (!nextMessage) {
+ Log.warn('Couldnt find next message.');
+ return;
+ }
- if (!nextMessage) {
- Log.warn('Couldnt find next message.');
- return;
- }
+ if (!nextMessage.isReplacement()) {
+ return nextMessage;
+ }
- return nextMessage;
+ nextId = nextMessage.getNextId();
+ }
}
private async generateElement() {
@@ -64,7 +70,7 @@ export default class ChatWindowMessage {
LinkHandlerGeo.get().detect(bodyElement);
- this.element.find('.jsxc-content').html(bodyElement);
+ this.element.find('.jsxc-content').html(bodyElement.get(0));
let timestampElement = this.element.find('.jsxc-timestamp');
DateTime.stringify(this.message.getStamp().getTime(), timestampElement);
@@ -85,6 +91,10 @@ export default class ChatWindowMessage {
this.element.addClass('jsxc-unread');
}
+ if (this.message.isReplacement()) {
+ this.element.addClass('jsxc-edited');
+ }
+
if (this.message.getErrorMessage()) {
this.element.addClass('jsxc-error');
this.element.find('.jsxc-error-content').text(Translation.t(this.message.getErrorMessage()));
@@ -219,6 +229,13 @@ export default class ChatWindowMessage {
}
private registerHooks() {
+ this.message.registerHook('replacedBy', () => {
+ const messageReplacement = this.message.getReplacedBy();
+ const chatWindowMessageReplacement = new ChatWindowMessage(messageReplacement, this.chatWindow);
+
+ this.element.replaceWith(chatWindowMessageReplacement.getElement());
+ });
+
this.message.registerHook('encrypted', encrypted => {
if (encrypted) {
this.element.addClass('jsxc-encrypted');
diff --git a/template/chat-window-message.hbs b/template/chat-window-message.hbs
index f0db247a..a16d9f2e 100644
--- a/template/chat-window-message.hbs
+++ b/template/chat-window-message.hbs
@@ -3,6 +3,7 @@
<div class="jsxc-chatmessage__footer">
<div class="jsxc-sender"></div>
<div class="jsxc-timestamp"></div>
+ <div class="jsxc-version"></div>
<div class="jsxc-transfer"></div>
<div class="jsxc-mark"></div>
</div>