Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(RichTextEditor): analytics [WPB-15304] #18570

Merged
merged 9 commits into from
Jan 10, 2025
11 changes: 11 additions & 0 deletions src/script/conversation/MessageRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import {
} from 'Util/LinkPreviewSender';
import {Declension, joinNames, t} from 'Util/LocalizerUtil';
import {getLogger, Logger} from 'Util/Logger';
import {isMarkdownText} from 'Util/MarkdownUtil';
import {areMentionsDifferent, isTextDifferent} from 'Util/messageComparator';
import {roundLogarithmic} from 'Util/NumberUtil';
import {matchQualifiedIds} from 'Util/QualifiedId';
Expand Down Expand Up @@ -1509,7 +1510,10 @@ export class MessageRepository {
}

const messageContentType = genericMessage.content;

let actionType;
let isRichText: boolean | undefined = undefined;

switch (messageContentType) {
case 'asset': {
const protoAsset = genericMessage.asset;
Expand Down Expand Up @@ -1548,6 +1552,9 @@ export class MessageRepository {
if (!length) {
actionType = 'text';
}
if (protoText) {
isRichText = isMarkdownText(protoText.content);
}
break;
}

Expand All @@ -1571,7 +1578,11 @@ export class MessageRepository {
[Segmentation.CONVERSATION.TYPE]: trackingHelpers.getConversationType(conversationEntity),
[Segmentation.CONVERSATION.SERVICES]: roundLogarithmic(services, 6),
[Segmentation.MESSAGE.ACTION]: actionType,
...(isRichText !== undefined && {
[Segmentation.IS_RICH_TEXT]: isRichText,
}),
};

const isTeamConversation = !!conversationEntity.teamId;
if (isTeamConversation) {
segmentations = {
Expand Down
1 change: 1 addition & 0 deletions src/script/tracking/Segmentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export const Segmentation = {
IS_REPLY: 'message_is_reply',
MENTION: 'message_mention',
},
IS_RICH_TEXT: 'is_rich_text',
SCREEN_SHARE: {
DIRECTION: 'screen_share_direction',
DURATION: 'screen_share_duration',
Expand Down
116 changes: 116 additions & 0 deletions src/script/util/MarkdownUtil.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Wire
* Copyright (C) 2025 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {isMarkdownText} from './MarkdownUtil';

describe('MarkdownUtil', () => {
describe('isMarkdownText', () => {
it('returns false for empty text', () => {
expect(isMarkdownText('')).toBe(false);
});

it('returns true for headers', () => {
expect(isMarkdownText('# Header')).toBe(true);
expect(isMarkdownText('## Header')).toBe(true);
expect(isMarkdownText('###### Header')).toBe(true);
});

it('returns true for bold text', () => {
expect(isMarkdownText('**bold**')).toBe(true);
expect(isMarkdownText('__bold__')).toBe(true);
});

it('returns true for italic text', () => {
expect(isMarkdownText('*italic*')).toBe(true);
expect(isMarkdownText('_italic_')).toBe(true);
});

it('returns true for links', () => {
expect(isMarkdownText('[example](http://example.com)')).toBe(true);
});

it('returns true for images', () => {
expect(isMarkdownText('![alt text](image.jpg)')).toBe(true);
});

it('returns true for unordered lists', () => {
expect(isMarkdownText('- item')).toBe(true);
expect(isMarkdownText('* item')).toBe(true);
expect(isMarkdownText('+ item')).toBe(true);
});

it('returns true for ordered lists', () => {
expect(isMarkdownText('1. item')).toBe(true);
});

it('returns true for blockquotes', () => {
expect(isMarkdownText('> quote')).toBe(true);
});

it('returns true for code blocks', () => {
expect(isMarkdownText('```\ncode\n```')).toBe(true);
expect(isMarkdownText('`code`')).toBe(true);
});

it('returns true for horizontal rules', () => {
expect(isMarkdownText('---')).toBe(true);
expect(isMarkdownText('***')).toBe(true);
expect(isMarkdownText('___')).toBe(true);
});

it('returns true for tables', () => {
expect(isMarkdownText('| Header |')).toBe(true);
expect(isMarkdownText('|---|')).toBe(true);
});

it('returns true for strikethrough', () => {
expect(isMarkdownText('~~strikethrough~~')).toBe(true);
});

it('returns false for plain text', () => {
expect(isMarkdownText('plain text')).toBe(false);
});

it('returns true for a mix of Markdown features', () => {
expect(isMarkdownText('# Header with [link](http://example.com)')).toBe(true);
expect(isMarkdownText('**Bold and _italic_**')).toBe(true);
expect(isMarkdownText('- item with `inline code`')).toBe(true);
expect(isMarkdownText('> Quote with *italic*')).toBe(true);
});

it('returns true for multi-line Markdown', () => {
expect(
isMarkdownText(`\`\`\`
Line 1
Line 2
\`\`\``),
).toBe(true);
expect(
isMarkdownText(`1. Item 1
2. Item 2`),
).toBe(true);
});

it('handles escaped Markdown patterns correctly', () => {
expect(isMarkdownText('\\*not italic\\*')).toBe(false);
expect(isMarkdownText('Some \\`inline code\\` here')).toBe(false);
expect(isMarkdownText('\\> Not a blockquote')).toBe(false);
});
});
});
75 changes: 75 additions & 0 deletions src/script/util/MarkdownUtil.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Wire
* Copyright (C) 2025 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

export const isMarkdownText = (text: string): boolean => {
if (!text) {
return false;
}

const markdownPatterns = [
// Headers (e.g. # Header)
/^#{1,6}\s+/m,

// Bold (e.g. **bold** or __bold__)
/\*\*[^*]+\*\*/,
/__[^_]+__/,

// Italic (e.g. *italic* or _italic_)
/\*[^*]+\*/,
/_[^_]+_/,

// Links (e.g. [text](http://example.com))
/\[[^\]\r\n]{0,500}\]\([^()\r\n]{0,1000}\)/,

// Images (e.g. ![alt](url))
/!\[[^\]]*\]\([^)]*\)/,

// Lists
/^[-*+]\s[^\n]*$/m, // Unordered (e.g. - item, * item)
/^\d+\.\s[^\n]*$/m, // Ordered (e.g. 1. item)

// Blockquotes (e.g. > quote)
/^>\s+/m,

// Code blocks (e.g. ``` code ``` or `inline code`)
/```[\s\S]*?```/,
/`[^`]+`/,

// Horizontal rules (e.g. --- or *** or ___)
/^(?:[-*_]){3,}\s*$/m,

// Tables (e.g. | Header | row | --- | :---: |)
/\|[^|]+\|/,
/^[-:|]+$/m,

// Strikethrough (e.g., ~~text~~)
/~~[^~]+~~/,
];

const invalidPatterns = [
// Escaped markdown characters (\*not italic\*)
/\\([\\`*_{}[\]()#+\-.!>])/,
];

if (invalidPatterns.some(pattern => pattern.test(text))) {
return false;
}

return markdownPatterns.some(pattern => pattern.test(text));
};
Loading