mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-25 19:15:18 +02:00
refactor: improve inline mention editor by enhancing chip element interactions and responsiveness for mobile and desktop devices
This commit is contained in:
parent
3be26429ca
commit
58f620482f
1 changed files with 68 additions and 44 deletions
|
|
@ -170,59 +170,83 @@ export const InlineMentionEditor = forwardRef<InlineMentionEditorRef, InlineMent
|
||||||
// Create a chip element for a document
|
// Create a chip element for a document
|
||||||
const createChipElement = useCallback(
|
const createChipElement = useCallback(
|
||||||
(doc: MentionedDocument): HTMLSpanElement => {
|
(doc: MentionedDocument): HTMLSpanElement => {
|
||||||
const chip = document.createElement("span");
|
const chip = document.createElement("span");
|
||||||
chip.setAttribute(CHIP_DATA_ATTR, "true");
|
chip.setAttribute(CHIP_DATA_ATTR, "true");
|
||||||
chip.setAttribute(CHIP_ID_ATTR, String(doc.id));
|
chip.setAttribute(CHIP_ID_ATTR, String(doc.id));
|
||||||
chip.setAttribute(CHIP_DOCTYPE_ATTR, doc.document_type ?? "UNKNOWN");
|
chip.setAttribute(CHIP_DOCTYPE_ATTR, doc.document_type ?? "UNKNOWN");
|
||||||
chip.contentEditable = "false";
|
chip.contentEditable = "false";
|
||||||
chip.className =
|
chip.className =
|
||||||
"inline-flex items-center gap-1 mx-0.5 pl-1 pr-0.5 py-0.5 rounded bg-primary/10 text-xs font-bold text-primary/60 select-none";
|
"inline-flex items-center gap-1 mx-0.5 px-1 py-0.5 rounded bg-primary/10 text-xs font-bold text-primary/60 select-none cursor-default";
|
||||||
chip.style.userSelect = "none";
|
chip.style.userSelect = "none";
|
||||||
chip.style.verticalAlign = "baseline";
|
chip.style.verticalAlign = "baseline";
|
||||||
|
|
||||||
// Add document type icon
|
// Container that swaps between icon and remove button on hover
|
||||||
const iconSpan = document.createElement("span");
|
const iconContainer = document.createElement("span");
|
||||||
iconSpan.className = "shrink-0 flex items-center text-muted-foreground";
|
iconContainer.className = "shrink-0 flex items-center size-3 relative";
|
||||||
iconSpan.innerHTML = ReactDOMServer.renderToString(
|
|
||||||
getConnectorIcon(doc.document_type ?? "UNKNOWN", "h-3 w-3")
|
|
||||||
);
|
|
||||||
|
|
||||||
const titleSpan = document.createElement("span");
|
const iconSpan = document.createElement("span");
|
||||||
titleSpan.className = "max-w-[120px] truncate";
|
iconSpan.className = "flex items-center text-muted-foreground";
|
||||||
titleSpan.textContent = doc.title;
|
iconSpan.innerHTML = ReactDOMServer.renderToString(
|
||||||
titleSpan.title = doc.title;
|
getConnectorIcon(doc.document_type ?? "UNKNOWN", "h-3 w-3")
|
||||||
titleSpan.setAttribute("data-mention-title", "true");
|
);
|
||||||
|
|
||||||
const statusSpan = document.createElement("span");
|
const removeBtn = document.createElement("button");
|
||||||
statusSpan.setAttribute(CHIP_STATUS_ATTR, "true");
|
removeBtn.type = "button";
|
||||||
statusSpan.className = "text-[10px] font-semibold opacity-80 hidden";
|
removeBtn.className =
|
||||||
|
"size-3 items-center justify-center rounded-full text-muted-foreground transition-colors";
|
||||||
|
removeBtn.style.display = "none";
|
||||||
|
removeBtn.innerHTML = ReactDOMServer.renderToString(
|
||||||
|
createElement(X, { className: "h-3 w-3", strokeWidth: 2.5 })
|
||||||
|
);
|
||||||
|
removeBtn.onclick = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
chip.remove();
|
||||||
|
const docKey = `${doc.document_type ?? "UNKNOWN"}:${doc.id}`;
|
||||||
|
setMentionedDocs((prev) => {
|
||||||
|
const next = new Map(prev);
|
||||||
|
next.delete(docKey);
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
onDocumentRemove?.(doc.id, doc.document_type);
|
||||||
|
focusAtEnd();
|
||||||
|
};
|
||||||
|
|
||||||
const removeBtn = document.createElement("button");
|
const titleSpan = document.createElement("span");
|
||||||
removeBtn.type = "button";
|
titleSpan.className = "max-w-[120px] truncate";
|
||||||
removeBtn.className =
|
titleSpan.textContent = doc.title;
|
||||||
"size-3 flex items-center justify-center rounded-full hover:bg-primary/20 transition-colors ml-0.5";
|
titleSpan.title = doc.title;
|
||||||
removeBtn.innerHTML = ReactDOMServer.renderToString(
|
titleSpan.setAttribute("data-mention-title", "true");
|
||||||
createElement(X, { className: "h-2.5 w-2.5", strokeWidth: 2.5 })
|
|
||||||
);
|
|
||||||
removeBtn.onclick = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
chip.remove();
|
|
||||||
const docKey = `${doc.document_type ?? "UNKNOWN"}:${doc.id}`;
|
|
||||||
setMentionedDocs((prev) => {
|
|
||||||
const next = new Map(prev);
|
|
||||||
next.delete(docKey);
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
// Notify parent that a document was removed
|
|
||||||
onDocumentRemove?.(doc.id, doc.document_type);
|
|
||||||
focusAtEnd();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const statusSpan = document.createElement("span");
|
||||||
|
statusSpan.setAttribute(CHIP_STATUS_ATTR, "true");
|
||||||
|
statusSpan.className = "text-[10px] font-semibold opacity-80 hidden";
|
||||||
|
|
||||||
|
const isTouchDevice = window.matchMedia("(hover: none)").matches;
|
||||||
|
if (isTouchDevice) {
|
||||||
|
// Mobile: icon on left, title, X on right
|
||||||
chip.appendChild(iconSpan);
|
chip.appendChild(iconSpan);
|
||||||
chip.appendChild(titleSpan);
|
chip.appendChild(titleSpan);
|
||||||
chip.appendChild(statusSpan);
|
chip.appendChild(statusSpan);
|
||||||
|
removeBtn.style.display = "flex";
|
||||||
|
removeBtn.className += " ml-0.5";
|
||||||
chip.appendChild(removeBtn);
|
chip.appendChild(removeBtn);
|
||||||
|
} else {
|
||||||
|
// Desktop: icon/X swap on hover in the same slot
|
||||||
|
iconContainer.appendChild(iconSpan);
|
||||||
|
iconContainer.appendChild(removeBtn);
|
||||||
|
chip.addEventListener("mouseenter", () => {
|
||||||
|
iconSpan.style.display = "none";
|
||||||
|
removeBtn.style.display = "flex";
|
||||||
|
});
|
||||||
|
chip.addEventListener("mouseleave", () => {
|
||||||
|
iconSpan.style.display = "";
|
||||||
|
removeBtn.style.display = "none";
|
||||||
|
});
|
||||||
|
chip.appendChild(iconContainer);
|
||||||
|
chip.appendChild(titleSpan);
|
||||||
|
chip.appendChild(statusSpan);
|
||||||
|
}
|
||||||
|
|
||||||
return chip;
|
return chip;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue