diff --git a/.eslintrc.json b/.eslintrc.json index c048b97b0f..890eb37b4c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -97,7 +97,7 @@ ], "lines-around-comment": "off", "lines-around-directive": "off", - "max-depth": "error", + "max-depth": "off", "max-len": "off", "max-lines": "off", "max-nested-callbacks": "off", @@ -192,7 +192,7 @@ "no-undef-init": "error", "no-undefined": "off", "no-underscore-dangle": "off", - "no-unmodified-loop-condition": "error", + "no-unmodified-loop-condition": "off", "no-unneeded-ternary": "off", "no-unused-vars": "error", "no-unused-expressions": "off", diff --git a/CHANGES.md b/CHANGES.md index 8b22324b5c..085e9c807a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,8 @@ configuration settings should now be accessed via `_converse.api.settings.get` and not directly on the `_converse` object. Soon we'll deprecate the latter, so prepare now. +- #515 Add support for XEP-0050 Ad-Hoc commands +- #1083 Add support for XEP-0393 Message Styling - #2231: add sort_by_query and remove sort_by_length - #1313: Stylistic improvements to the send button - #1481: MUC OMEMO: Error No record for device @@ -15,7 +17,6 @@ Soon we'll deprecate the latter, so prepare now. - #1793: Send button doesn't appear in Firefox in 1:1 chats - #1820: Set focus on jid field after controlbox is loaded - #1822: Don't log error if user has no bookmarks -- #515 Add support for XEP-0050 Ad-Hoc commands - #1823: New config options [muc_roomid_policy](https://conversejs.org/docs/html/configuration.html#muc-roomid-policy) and [muc_roomid_policy_hint](https://conversejs.org/docs/html/configuration.html#muc-roomid-policy-hint) - #1826: A user can now add himself as a contact diff --git a/README.md b/README.md index d4a9dc8a44..36e29afb3c 100644 --- a/README.md +++ b/README.md @@ -101,6 +101,7 @@ In embedded mode, Converse can be embedded into an element in the DOM. - [XEP-0372](https://xmpp.org/extensions/xep-0372.html) References - [XEP-0382](https://xmpp.org/extensions/xep-0382.html) Spoiler messages - [XEP-0384](https://xmpp.org/extensions/xep-0384.html) OMEMO Encryption +- [XEP-0393](https://xmpp.org/extensions/xep-0393.html) Message styling - [XEP-0422](https://xmpp.org/extensions/xep-0422.html) Message Fastening (limited support) - [XEP-0424](https://xmpp.org/extensions/xep-0424.html) Message Retractions - [XEP-0425](https://xmpp.org/extensions/xep-0425.html) Message Moderation diff --git a/index.html b/index.html index f4a73102a6..53e71404f6 100644 --- a/index.html +++ b/index.html @@ -197,6 +197,7 @@
Here\'s *some* code
`');
+
+ msg_text = "This *is not a styling hint \n * _But this is_!";
+ msg = mock.createChatMessage(_converse, contact_jid, msg_text)
+ await _converse.handleMessageStanza(msg);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
+ expect(msg_el.innerText).toBe(msg_text);
+ await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') ===
+ "This *is not a styling hint \n * _But this is_!");
+
+ msg_text = "Here's a ~strikethrough section~";
+ msg = mock.createChatMessage(_converse, contact_jid, msg_text)
+ await _converse.handleMessageStanza(msg);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
+ expect(msg_el.innerText).toBe(msg_text);
+ await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') ===
+ "Here's a ~hello
we don't enable *styling hints* like ~these~\n\`\`\``;
+ msg = mock.createChatMessage(_converse, contact_jid, msg_text)
+ await _converse.handleMessageStanza(msg);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
+ expect(msg_el.innerText).toBe(msg_text);
+ await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') ===
+ 'Here\'s a code block: ```\nInside the code-block, <code>hello</code> we don\'t enable *styling hints* like ~these~\n
```'
+ );
+
+ msg_text = `> This is quoted text\n>This is also quoted\nThis is not quoted`;
+ msg = mock.createChatMessage(_converse, contact_jid, msg_text)
+ await _converse.handleMessageStanza(msg);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
+ expect(msg_el.innerText).toBe(msg_text);
+ await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === 'This is quoted text\nThis is also quoted\nThis is not quoted'); + + msg_text = `> This is *quoted* text\n>This is \`also _quoted_\`\nThis is not quoted`; + msg = mock.createChatMessage(_converse, contact_jid, msg_text) + await _converse.handleMessageStanza(msg); + await new Promise(resolve => view.model.messages.once('rendered', resolve)); + msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop(); + expect(msg_el.innerText).toBe(msg_text); + await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === + "
This is *quoted* text\nThis is `also _quoted_
`\n
This is not quoted");
+
+ msg_text = `(There are three blocks in this body marked by parens,)\n (but there is no *formatting)\n (as spans* may not escape blocks.)`;
+ msg = mock.createChatMessage(_converse, contact_jid, msg_text)
+ await _converse.handleMessageStanza(msg);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
+ expect(msg_el.innerText).toBe(msg_text);
+ await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === msg_text);
+
+ msg_text = "```ignored\n (println \"Hello, world!\")\n ```\n\n This should show up as monospace, preformatted text ^";
+ msg = mock.createChatMessage(_converse, contact_jid, msg_text)
+ await _converse.handleMessageStanza(msg);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
+ expect(msg_el.innerText).toBe(msg_text);
+ await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') ===
+ "```ignored\n (println \"Hello, world!\")\n
```\n\n This should show up as monospace, preformatted text ^");
+
+ msg_text = ">```ignored\n> (println \"Hello, world!\")\n> ```\n>\n> This should show up as monospace, preformatted text ^";
+ msg = mock.createChatMessage(_converse, contact_jid, msg_text)
+ await _converse.handleMessageStanza(msg);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
+ expect(msg_el.innerText).toBe(msg_text);
+ await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') ===
+ "```ignored\n <span></span> (println \"Hello, world!\")\n
```\n\n This should show up as monospace, preformatted text ^
");
+
+ msg_text = `> > This is doubly quoted text`;
+ msg = mock.createChatMessage(_converse, contact_jid, msg_text)
+ await _converse.handleMessageStanza(msg);
+ await new Promise(resolve => view.model.messages.once('rendered', resolve));
+ msg_el = Array.from(view.el.querySelectorAll('converse-chat-message-body')).pop();
+ expect(msg_el.innerText).toBe(msg_text);
+ await u.waitUntil(() => msg_el.innerHTML.replace(//g, '') === ""); + + done(); + })); +}); diff --git a/src/headless/utils/parse-helpers.js b/src/headless/utils/parse-helpers.js index 8036f3e537..d3813280be 100644 --- a/src/headless/utils/parse-helpers.js +++ b/src/headless/utils/parse-helpers.js @@ -4,6 +4,7 @@ * @description Pure functions to help funcitonally parse messages. * @todo Other parsing helpers can be made more abstract and placed here. */ + const helpers = {}; // Captures all mentions, but includes a space before the @ @@ -41,3 +42,144 @@ const reduceReferences = ([text, refs], ref, index) => { helpers.reduceTextFromReferences = (text, refs) => refs.reduce(reduceReferences, [text, []]); export default helpers; + +const styling_directives = ['*', '_', '~', '`', '```', '>', '>']; +const recursive_directives = ['*', '_', '~', '>', '>']; +const styling_map = { + '*': {'name': 'strong', 'type': 'span'}, + '_': {'name': 'emphasis', 'type': 'span'}, + '~': {'name': 'strike', 'type': 'span'}, + '`': {'name': 'preformatted', 'type': 'span'}, + '```': {'name': 'preformatted_block', 'type': 'block'}, + '>': {'name': 'quote', 'type': 'block'}, + '>': {'name': 'quote', 'type': 'block'} +}; + +const styling_templates = { + emphasis: (text) => `${text}`, + preformatted: (text) => `This is doubly quoted text
${text}
`,
+ preformatted_block: (text) => `${text}
`,
+ quote: (text) => `${text}`, + strike: (text) => `