blob: 90ff24d6833f35a90f591d91709aa0b0d55df621 [file] [log] [blame]
Adrià Vilanova Martínez5b3590f2021-12-26 13:05:10 +01001import {MDCTooltip} from '@material/tooltip';
Adrià Vilanova Martínez72a4ea42024-02-09 22:35:32 +01002import {waitFor} from 'poll-until-promise';
Adrià Vilanova Martínez5b3590f2021-12-26 13:05:10 +01003
avm999632485a3e2021-09-08 22:18:38 +02004import {createPlainTooltip} from '../../../common/tooltip.js';
5
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +02006export function removeChildNodes(node) {
Adrià Vilanova Martínez08a760b2023-02-03 18:47:30 +01007 while (node?.firstChild) {
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +02008 node.removeChild(node.firstChild);
9 }
10}
11
12export function getNParent(node, n) {
13 if (n <= 0) return node;
14 if (!('parentNode' in node)) return null;
15 return getNParent(node.parentNode, n - 1);
16}
17
18export function createExtBadge() {
avm999632485a3e2021-09-08 22:18:38 +020019 let badge = document.createElement('div');
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020020 badge.classList.add('TWPT-badge');
avm999632485a3e2021-09-08 22:18:38 +020021 let badgeTooltip = createPlainTooltip(
22 badge,
23 chrome.i18n.getMessage(
24 'inject_extension_badge_helper', [chrome.i18n.getMessage('appName')]),
25 false);
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020026
avm999632485a3e2021-09-08 22:18:38 +020027 let badgeI = document.createElement('i');
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020028 badgeI.classList.add('material-icon-i', 'material-icons-extended');
29 badgeI.textContent = 'repeat';
30
31 badge.append(badgeI);
avm999632485a3e2021-09-08 22:18:38 +020032 return [badge, badgeTooltip];
Adrià Vilanova Martínez3465e772021-07-11 19:18:41 +020033}
Adrià Vilanova Martínez5b3590f2021-12-26 13:05:10 +010034
Adrià Vilanova Martínez72a4ea42024-02-09 22:35:32 +010035/**
36 * Adds an element to the thread list actions bar next to the button given by
37 * |originalBtn|.
38 */
Adrià Vilanova Martínez2788d122022-10-10 22:06:25 +020039export function addElementToThreadListActions(originalBtn, element) {
40 var duplicateBtn =
41 originalBtn.parentNode.querySelector('[debugid="mark-duplicate-button"]');
42 if (duplicateBtn)
43 duplicateBtn.parentNode.insertBefore(
44 element, (duplicateBtn.nextSibling || duplicateBtn));
45 else
46 originalBtn.parentNode.insertBefore(
47 element, (originalBtn.nextSibling || originalBtn));
48}
49
Adrià Vilanova Martínez72a4ea42024-02-09 22:35:32 +010050/**
51 * Adds a button to the thread list actions bar next to the button given by
52 * |originalBtn|. The button will have icon |icon|, when hovered it will display
53 * |tooltip|, and will have a debugid attribute with value |debugId|.
54 */
Adrià Vilanova Martínez1e10d192021-12-31 16:01:13 +010055export function addButtonToThreadListActions(
56 originalBtn, icon, debugId, tooltip) {
Adrià Vilanova Martínez5b3590f2021-12-26 13:05:10 +010057 let clone = originalBtn.cloneNode(true);
58 clone.setAttribute('debugid', debugId);
59 clone.classList.add('TWPT-btn--with-badge');
60 clone.querySelector('material-icon').setAttribute('icon', icon);
61 clone.querySelector('i.material-icon-i').textContent = icon;
62
63 let badge, badgeTooltip;
64 [badge, badgeTooltip] = createExtBadge();
65 clone.append(badge);
66
Adrià Vilanova Martínez2788d122022-10-10 22:06:25 +020067 addElementToThreadListActions(originalBtn, clone);
Adrià Vilanova Martínez5b3590f2021-12-26 13:05:10 +010068
69 createPlainTooltip(clone, tooltip);
70 new MDCTooltip(badgeTooltip);
71
72 return clone;
73}
Adrià Vilanova Martínez1e10d192021-12-31 16:01:13 +010074
Adrià Vilanova Martínez72a4ea42024-02-09 22:35:32 +010075/**
76 * Returns true if |node| is the "mark as read/unread" button, the parent of the
77 * parent of |node| is the actions bar of the thread list, and the button with
78 * debugid |debugid| is NOT part of the actions bar.
79 */
Adrià Vilanova Martínez4107b5e2022-10-09 23:11:11 +020080export function shouldAddBtnToActionBar(debugid, node) {
81 return node?.tagName == 'MATERIAL-BUTTON' &&
82 (node.getAttribute?.('debugid') == 'mark-read-button' ||
83 node.getAttribute?.('debugid') == 'mark-unread-button') &&
84 node.parentNode?.querySelector('[debugid="' + debugid + '"]') === null &&
85 node.parentNode?.parentNode?.tagName == 'EC-BULK-ACTIONS';
86}
87
Adrià Vilanova Martínez72a4ea42024-02-09 22:35:32 +010088/**
89 * Returns the display language set by the user.
90 */
Adrià Vilanova Martínez4f56d562022-01-26 00:23:27 +010091export function getDisplayLanguage() {
92 var startup =
93 JSON.parse(document.querySelector('html').getAttribute('data-startup'));
94 return startup?.[1]?.[1]?.[3]?.[6] ?? 'en';
95}
Adrià Vilanova Martínez2e633142022-12-28 00:48:41 +010096
Adrià Vilanova Martínez72a4ea42024-02-09 22:35:32 +010097/**
98 * Refreshes the current view in the Community Console without reloading the
99 * whole page if possible.
100 */
Adrià Vilanova Martínez2e633142022-12-28 00:48:41 +0100101export function softRefreshView() {
102 const refreshButton = document.querySelector('.app-title-button');
103 if (refreshButton == null)
104 window.location.reload();
105 else
106 refreshButton.click();
107}
Adrià Vilanova Martínez72a4ea42024-02-09 22:35:32 +0100108
109/**
110 * Opens the vanilla Community Console reply editor.
111 * @param {string} messageId The id of the message for which to open the reply
112 * editor.
113 * @returns {Promise<Element>} A promise resolving to the reply editor.
114 */
115export async function openReplyEditor(messageId) {
116 const nodeReply =
117 document.querySelector('[data-twpt-message-id="' + messageId + '"]')
118 ?.closest?.('sc-tailwind-thread-message-message-card');
119 const nodeReplyButton = nodeReply?.querySelector?.(
120 '.scTailwindThreadMessageMessagecardadd-comment button');
121 if (!nodeReplyButton) {
122 // This is not critical: the reply button might already have been clicked
123 // (so it no longer exists), or the thread might be locked so replying is
124 // disabled and the button doesn't exist.
125 console.debug('[flattenThreads] Reply button not found.');
126 return null;
127 }
128
129 nodeReplyButton.click();
130
131 return await waitFor(() => {
132 const editor = nodeReply?.querySelector('sc-tailwind-thread-reply-editor');
133 if (editor) return Promise.resolve(editor);
134 return Promise.reject(new Error('Editor not found.'));
135 }, {interval: 75, timeout: 10 * 1000});
136}