extraInfo: show extra info in the canned responses list
Bug: twpowertools:93
Change-Id: I8d81391cb931f2096c704b50ff4f3cc9037b8c6e
diff --git a/src/common/xhrInterceptors.json5 b/src/common/xhrInterceptors.json5
index 2e9a443..126b6fb 100644
--- a/src/common/xhrInterceptors.json5
+++ b/src/common/xhrInterceptors.json5
@@ -20,5 +20,10 @@
urlRegex: "api/ViewUnifiedUser",
intercepts: "response",
},
+ {
+ eventName: "ListCannedResponsesResponse",
+ urlRegex: "api/ListCannedResponses",
+ intercepts: "response",
+ },
],
}
diff --git a/src/contentScripts/communityConsole/extraInfo.js b/src/contentScripts/communityConsole/extraInfo.js
index c572523..266592a 100644
--- a/src/contentScripts/communityConsole/extraInfo.js
+++ b/src/contentScripts/communityConsole/extraInfo.js
@@ -2,10 +2,12 @@
import {waitFor} from 'poll-until-promise';
import {isOptionEnabled} from '../../common/optionsUtils.js';
+import {createPlainTooltip} from '../../common/tooltip.js';
import {createExtBadge} from './utils/common.js';
const kViewUnifiedUserResponseEvent = 'TWPT_ViewUnifiedUserResponse';
+const kListCannedResponsesResponse = 'TWPT_ListCannedResponsesResponse';
const kAbuseCategories = [
['1', 'Account'],
@@ -147,6 +149,11 @@
id: -1,
timestamp: 0,
};
+ this.lastCRsList = {
+ body: {},
+ id: -1,
+ duplicateNames: new Set(),
+ };
this.setUpHandlers();
}
@@ -160,6 +167,27 @@
timestamp: Date.now(),
};
});
+ window.addEventListener(kListCannedResponsesResponse, e => {
+ if (e.detail.id < this.lastCRsList.id) return;
+
+ // Look if there are duplicate names
+ const crs = e.detail.body?.['1'] ?? [];
+ const names = crs.map(cr => cr?.['7']).slice().sort();
+ let duplicateNames = new Set();
+ for (let i = 1; i < names.length; i++)
+ if (names[i - 1] == names[i]) duplicateNames.add(names[i]);
+
+ this.lastCRsList = {
+ body: e.detail.body,
+ id: e.detail.id,
+ duplicateNames,
+ };
+ });
+ }
+
+ // Whether the feature is enabled
+ isEnabled() {
+ return isOptionEnabled('extrainfo');
}
// Add a pretty component which contains |info| to |node|.
@@ -173,8 +201,7 @@
let badgeCell = document.createElement('div');
badgeCell.classList.add('TWPT-extrainfo-badge-cell');
- let badge, badgeTooltip;
- [badge, badgeTooltip] = createExtBadge();
+ const [badge, badgeTooltip] = createExtBadge();
badgeCell.append(badge);
let infoCell = document.createElement('div');
@@ -250,8 +277,79 @@
}
injectAtProfileIfEnabled(card) {
- isOptionEnabled('extrainfo').then(isEnabled => {
+ this.isEnabled().then(isEnabled => {
if (isEnabled) return this.injectAtProfile(card);
});
}
+
+ // Canned responses (CRs) functionality
+
+ getCRName(tags, isExpanded) {
+ if (!isExpanded)
+ return tags.parentNode?.querySelector?.('.text .name')?.textContent;
+
+ // https://www.youtube.com/watch?v=Z6_ZNW1DACE
+ return tags.parentNode?.parentNode?.parentNode?.parentNode?.parentNode
+ ?.parentNode?.parentNode?.querySelector?.('.text .name')
+ ?.textContent;
+ }
+
+ // Inject usage stats in the |tags| component of a CR
+ injectAtCR(tags, isExpanded) {
+ waitFor(() => {
+ if (this.lastCRsList.id != -1) return Promise.resolve(this.lastCRsList);
+ return Promise.reject('Didn\'t receive canned responses list');
+ }, {
+ interval: 500,
+ timeout: 15 * 1000,
+ }).then(crs => {
+ let name = this.getCRName(tags, isExpanded);
+
+ // If another CR has the same name, there's no easy way to distinguish
+ // them, so don't show the usage stats.
+ if (crs.duplicateNames.has(name)) return;
+
+ for (const cr of (crs.body?.['1'] ?? [])) {
+ if (cr['7'] == name) {
+ let tag = document.createElement('material-chip');
+ tag.classList.add('TWPT-tag');
+
+ let container = document.createElement('div');
+ container.classList.add('TWPT-chip-content-container');
+
+ let content = document.createElement('div');
+ content.classList.add('TWPT-content');
+
+ const [badge, badgeTooltip] = createExtBadge();
+
+ let label = document.createElement('span');
+ label.textContent = 'Used ' + (cr['8'] ?? '0') + ' times';
+
+ content.append(badge, label);
+ container.append(content);
+ tag.append(container);
+ tags.append(tag);
+
+ new MDCTooltip(badgeTooltip);
+
+ if (cr['9']) {
+ const lastUsedTime = Math.floor(parseInt(cr['9']) / 1e3);
+ let date = (new Date(lastUsedTime)).toLocaleString();
+ createPlainTooltip(label, 'Last used: ' + date);
+ }
+
+ break;
+ }
+ }
+ });
+ }
+
+ injectAtCRIfEnabled(tags, isExpanded) {
+ // If the tag has already been injected, exit.
+ if (tags.querySelector('.TWPT-tag')) return;
+
+ this.isEnabled().then(isEnabled => {
+ if (isEnabled) return this.injectAtCR(tags, isExpanded);
+ });
+ }
}
diff --git a/src/contentScripts/communityConsole/main.js b/src/contentScripts/communityConsole/main.js
index a534f38..73ed360 100644
--- a/src/contentScripts/communityConsole/main.js
+++ b/src/contentScripts/communityConsole/main.js
@@ -44,6 +44,10 @@
// Unified profile iframe
'iframe',
+
+ // Canned response tags or toolbelt (for the extra info feature)
+ '.tags',
+ '.toolbelt',
];
function handleCandidateNode(node) {
@@ -149,6 +153,15 @@
unifiedProfilesFix.checkIframe(node)) {
unifiedProfilesFix.fixIframe(node);
}
+
+ // Show additional details in the canned responses view.
+ if (node.matches('ec-canned-response-row .tags')) {
+ window.TWPTExtraInfo.injectAtCRIfEnabled(node, /* isExpanded = */ false);
+ }
+ if (node.matches('ec-canned-response-row .main .toolbelt')) {
+ const tags = node.parentNode?.querySelector?.('.tags');
+ if (tags) window.TWPTExtraInfo.injectAtCRIfEnabled(tags, /* isExpanded = */ true);
+ }
}
}
diff --git a/src/static/_locales/en/messages.json b/src/static/_locales/en/messages.json
index 2637808..0e765ec 100644
--- a/src/static/_locales/en/messages.json
+++ b/src/static/_locales/en/messages.json
@@ -152,7 +152,7 @@
"description": "Button in the options page which opens the workflow management page."
},
"options_extrainfo": {
- "message": "Show extra information in threads and profiles.",
+ "message": "Show additional information in threads, profiles and the canned responses list.",
"description": "Feature checkbox in the options page"
},
"options_save": {
diff --git a/src/static/css/ccdarktheme.css b/src/static/css/ccdarktheme.css
index aa49585..b8ea787 100644
--- a/src/static/css/ccdarktheme.css
+++ b/src/static/css/ccdarktheme.css
@@ -1502,7 +1502,8 @@
ec-canned-responses .label-row,
ec-canned-responses ec-canned-response-row .snippet,
- ec-canned-responses ec-canned-response-row .tag {
+ ec-canned-responses ec-canned-response-row .tag .content,
+ ec-canned-responses ec-canned-response-row .TWPT-tag .TWPT-content {
color: var(--TWPT-secondary-text)!important;
}
diff --git a/src/static/css/extrainfo.css b/src/static/css/extrainfo.css
index 86eed01..d04d42d 100644
--- a/src/static/css/extrainfo.css
+++ b/src/static/css/extrainfo.css
@@ -32,3 +32,38 @@
margin-top: 16px;
align-self: end;
}
+
+/* Special tags components for canned responses */
+ec-canned-response-row .TWPT-tag {
+ background-color: transparent;
+ color: #474747;
+ margin: 0 0 0 4px;
+ border: 1px solid #ababab;
+ display: flex;
+ align-items: center;
+ border-radius: 16px;
+ height: 32px;
+ overflow: hidden;
+}
+
+ec-canned-response-row .TWPT-chip-content-container {
+ margin: 0 12px;
+ overflow: hidden;
+ white-space: nowrap;
+}
+
+ec-canned-response-row .TWPT-content {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: flex;
+ align-items: center;
+}
+
+ec-canned-response-row .TWPT-content .TWPT-badge {
+ --icon-size: 14px;
+ margin-right: 6px;
+}
+
+ec-canned-response-row .TWPT-content span[aria-describedby] {
+ cursor: help;
+}