diff --git a/README.md b/README.md
index d78e43f..537aa71 100644
--- a/README.md
+++ b/README.md
@@ -185,6 +185,7 @@ The following namespaces are available for use:
- `message:reactions:view`: show the list of reactions that can be sent for message
- `message:reactions:react`: react to a message (eg. user clicks on an existing reaction to send it or retract it)
- `message:file:view`: view a file from a message (eg. user clicks on a file to expand or download the file)
+- `message:link:open`: open a link contained in a message (eg. user clicks on a link)
- `message:history:view`: notify when a message from history enters or leaves view area, with the `visible` and `hidden` states (eg. scrolling up or down in the timeline)
- `message:history:seek`: request to load `backwards` or `forwards` history (eg. scrolling back in time to load past messages)
diff --git a/src/messaging/components/message/message.js b/src/messaging/components/message/message.js
index aa25c02..ff287a7 100644
--- a/src/messaging/components/message/message.js
+++ b/src/messaging/components/message/message.js
@@ -7,6 +7,7 @@
// IMPORTS
+import { nextTick } from "petite-vue";
import { htmlEscape as _e } from "escape-goat";
import linkifyHtml from "linkify-html";
import snarkdown from "snarkdown";
@@ -241,6 +242,10 @@ function MessagePartText(content) {
mounted() {
// Generate text message HTML
this.html = this.__generateHTML(content);
+
+ // Bind link click events
+ // Notice: ensure DOM has been rendered w/ HTML content
+ nextTick(this.__bindLinkClickEvents);
},
/**
@@ -260,11 +265,72 @@ function MessagePartText(content) {
htmlContent = linkifyHtml(htmlContent, TEXT_LINKIFY_OPTIONS);
return htmlContent;
+ },
+
+ /**
+ * Binds link click events
+ * @private
+ * @return {undefined}
+ */
+ __bindLinkClickEvents() {
+ // Bind click event on all links? (if any text element)
+ // Notice: since we are generating custom HTML code outside of Vue, then \
+ // we cannot use the standard '@click' event and have to resort to \
+ // using the non-Vue 'addEventListener()'.
+ if (this.$refs.textInner) {
+ const linkElements =
+ this.$refs.textInner.getElementsByTagName("a") || [];
+
+ if (linkElements.length > 0) {
+ for (const linkElement of linkElements) {
+ linkElement.addEventListener("click", this.__onLinkClick);
+ }
+ }
+ }
+ },
+
+ // --> EVENT LISTENERS <--
+
+ /**
+ * Triggers when a link is clicked
+ * @private
+ * @param {object} event
+ * @return {undefined}
+ */
+ __onLinkClick(event) {
+ // Do not open link (let the implementing app choose what to do)
+ event.preventDefault();
+
+ // Handle link? (if any)
+ const linkUrl = event.target?.href || null;
+
+ if (linkUrl !== null) {
+ // Extract protocol from link (if any)
+ const protocolSeparatorIndex = linkUrl.indexOf(":");
+
+ let linkProtocol = null;
+
+ if (protocolSeparatorIndex > 0) {
+ linkProtocol = linkUrl
+ .substring(0, protocolSeparatorIndex)
+ .toLowerCase();
+ }
+
+ // Emit message link open event
+ $event._emit("message:link:open", {
+ id: content.id,
+
+ link: {
+ url: linkUrl,
+ protocol: linkProtocol
+ }
+ });
+ }
}
};
}
-function MessagePartFile(content, file) {
+function MessagePartFile(file) {
return {
// --> TEMPLATE <--
diff --git a/src/messaging/messaging.html b/src/messaging/messaging.html
index ba5dd7d..2bcaf21 100644
--- a/src/messaging/messaging.html
+++ b/src/messaging/messaging.html
@@ -138,7 +138,7 @@
@@ -220,7 +220,12 @@
-
+
({{ $context.i18n._.attributes.edited }})
diff --git a/src/messaging/stores/broker.js b/src/messaging/stores/broker.js
index 8c7dc1b..93ea043 100644
--- a/src/messaging/stores/broker.js
+++ b/src/messaging/stores/broker.js
@@ -16,6 +16,7 @@ function BrokerStore() {
"message:reactions:view",
"message:reactions:react",
"message:file:view",
+ "message:link:open",
"message:history:view",
"message:history:seek"
]),
diff --git a/types/messaging.ts b/types/messaging.ts
index 0c81881..2d1fb75 100644
--- a/types/messaging.ts
+++ b/types/messaging.ts
@@ -201,6 +201,15 @@ export declare interface EventMessageFileView {
};
}
+export declare interface EventMessageLinkOpen {
+ id: string;
+
+ link: {
+ url: string;
+ protocol: null | string;
+ };
+}
+
export declare interface EventMessageHistoryView {
id: string;
visibility: ViewVisibility;