Skip to content

Commit f3c8702

Browse files
committed
refactor: use core's chatPrefillFromUser hook instead of own click handler
PR #19 originally landed an entire user-list click handler inside ep_ai_chat. That logic was generic — clicking a user in the user list to prefill an @-mention is a discoverability win for any multi-user pad, AI or no AI — and as @JohnMcLear pointed out, it belongs in Etherpad core, not in this plugin. The generic feature is now ether/etherpad#7660. This commit shrinks ep_ai_chat's contribution to the only AI-specific bit: when the clicked author is the AI, override the default "@AI_Assistant " prefill with the configured trigger ("@ai " by default) so the input lands in a useful state. Implementation: - Drop the in-plugin click delegation, the chatbox/chaticon DOM poking, and the input-prefill logic. Core handles all of that uniformly now. - Add a chatPrefillFromUser client hook that returns the trigger string when the clicked authorId matches the AI's. The clientVars hook (added earlier in this PR) still exposes the trigger and authorId; nothing changes there. - On older cores the new hook is never fired, so this is a no-op on a stock install — graceful degradation. Net diff vs main is now: a tiny clientVars exposure on the server and a ~10-line client hook handler. Everything else is core's job.
1 parent 2382056 commit f3c8702

2 files changed

Lines changed: 18 additions & 64 deletions

File tree

ep.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
},
1010
"client_hooks": {
1111
"postAceInit": "ep_ai_chat/static/js/index",
12-
"chatSendMessage": "ep_ai_chat/static/js/index"
12+
"chatSendMessage": "ep_ai_chat/static/js/index",
13+
"chatPrefillFromUser": "ep_ai_chat/static/js/index"
1314
}
1415
}
1516
]

static/js/index.js

Lines changed: 16 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -9,78 +9,31 @@
99
let padEditor = null;
1010

1111
/**
12-
* postAceInit: store a reference to the ace editor for selection access,
13-
* and wire up the user-list click handler that prefills @mentions in
14-
* the chat input.
12+
* postAceInit: store a reference to the ace editor for selection access.
1513
*/
1614
exports.postAceInit = (hookName, context) => {
1715
padEditor = context.ace;
18-
installUserListClickHandler();
1916
};
2017

2118
/**
22-
* Wire up a click handler on the user list so clicking another editor
23-
* (or the AI Assistant) opens chat and prefills the input with an
24-
* @-mention of that author. Helps users discover the chat panel and
25-
* the @ai trigger without needing to be told "click Chat first."
19+
* chatPrefillFromUser: when Etherpad core prefills "@<name> " in the chat
20+
* input on a user-list click, swap in the configured trigger string for
21+
* the AI's row. Without this, clicking the AI's chip in the user list
22+
* would prefill "@AI_Assistant " which doesn't match anything the
23+
* server-side mention extractor recognises.
2624
*
27-
* Implementation notes:
28-
* - Uses a single delegated handler on document so it survives
29-
* Etherpad re-rendering the user list table.
30-
* - The AI's display name ("AI Assistant") would be a useless
31-
* @-mention; clientVars.ep_ai_chat.{trigger,authorId} let us swap
32-
* that in for the trigger string ("@ai") instead.
33-
* - "Open chat" is a click on #chaticon — the same affordance the
34-
* user would otherwise have to find themselves.
25+
* Requires the chatPrefillFromUser hook (Etherpad >= the version that
26+
* landed ether/etherpad#7660). On older cores this hook is silently
27+
* never called and clicks fall back to the default prefill.
3528
*/
36-
const installUserListClickHandler = () => {
37-
document.addEventListener('click', (event) => {
38-
try {
39-
const tr = event.target.closest && event.target.closest('#otheruserstable tr');
40-
if (!tr) return;
41-
const authorId = tr.getAttribute('data-authorId');
42-
if (!authorId) return;
43-
// Skip clicks on the swatch (color-picker) — those have their own
44-
// click semantics and shouldn't also prefill chat.
45-
if (event.target.closest('.usertdswatch')) return;
46-
47-
const ai = (window.clientVars && window.clientVars.ep_ai_chat) || {};
48-
const nameCell = tr.querySelector('.usertdname');
49-
const nameText = nameCell ? (nameCell.textContent || '').trim() : '';
50-
51-
const mention = (ai.authorId && authorId === ai.authorId)
52-
? (ai.trigger || '@ai')
53-
: `@${nameText.replace(/\s+/g, '_') || 'user'}`;
54-
55-
openChatAndPrefill(`${mention} `);
56-
event.preventDefault();
57-
} catch (e) { /* never break a click */ }
58-
}, true);
59-
};
60-
61-
const openChatAndPrefill = (text) => {
62-
const chatBox = document.querySelector('#chatbox');
63-
const visible = chatBox && chatBox.classList.contains('visible');
64-
if (!visible) {
65-
const chatIcon = document.querySelector('#chaticon');
66-
if (chatIcon) chatIcon.click();
67-
}
68-
// Allow the chat panel to settle before focusing/prefilling.
69-
setTimeout(() => {
70-
const input = document.querySelector('#chatinput');
71-
if (!input) return;
72-
const current = input.value || '';
73-
// Don't clobber what the user was typing. If the field is empty,
74-
// or already starts with a stale @-token, replace it. Otherwise
75-
// append.
76-
if (!current.trim() || /^@\S+\s*$/.test(current.trim())) {
77-
input.value = text;
78-
} else if (!current.includes(text.trim())) {
79-
input.value = `${current.trimEnd()} ${text}`;
29+
exports.chatPrefillFromUser = (hookName, context, cb) => {
30+
try {
31+
const ai = (window.clientVars && window.clientVars.ep_ai_chat) || {};
32+
if (ai.authorId && context && context.authorId === ai.authorId) {
33+
return cb(`${ai.trigger || '@ai'} `);
8034
}
81-
input.focus();
82-
try { input.setSelectionRange(input.value.length, input.value.length); } catch (_e) { /* */ }
83-
}, 50);
35+
} catch (_e) { /* fall through to default */ }
36+
return cb();
8437
};
8538

8639
/**

0 commit comments

Comments
 (0)