p2: add quote and code-block text formatting
All checks were successful
CI / test (push) Successful in 20s
All checks were successful
CI / test (push) Successful in 20s
This commit is contained in:
@@ -28,7 +28,7 @@ Legend:
|
||||
19. Stickers - `PARTIAL` (web sticker picker with preset pack + favorites)
|
||||
20. GIF - `PARTIAL` (web GIF picker with Tenor search + preset fallback + favorites)
|
||||
21. Message History/Search - `DONE` (history/pagination/chat+global search)
|
||||
22. Text Formatting - `PARTIAL` (bold/italic/underline/spoiler/mono/links supported; toolbar still evolving)
|
||||
22. Text Formatting - `PARTIAL` (bold/italic/underline/spoiler/mono/links + strikethrough + quote/code block; toolbar still evolving)
|
||||
23. Groups - `PARTIAL` (create/add/remove/invite link; advanced moderation partial)
|
||||
24. Roles - `DONE` (owner/admin/member)
|
||||
25. Admin Rights - `PARTIAL` (delete/ban-like remove/pin/edit info; full ban system limited)
|
||||
|
||||
@@ -872,6 +872,31 @@ export function MessageComposer() {
|
||||
});
|
||||
}
|
||||
|
||||
function insertQuoteBlock() {
|
||||
const textarea = textareaRef.current;
|
||||
if (!textarea) {
|
||||
return;
|
||||
}
|
||||
const start = textarea.selectionStart ?? text.length;
|
||||
const end = textarea.selectionEnd ?? text.length;
|
||||
const selected = text.slice(start, end);
|
||||
const source = selected || "quote";
|
||||
const quoted = source
|
||||
.split("\n")
|
||||
.map((line) => `> ${line}`)
|
||||
.join("\n");
|
||||
const nextValue = `${text.slice(0, start)}${quoted}${text.slice(end)}`;
|
||||
setText(nextValue);
|
||||
if (activeChatId) {
|
||||
setDraft(activeChatId, nextValue);
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
textarea.focus();
|
||||
const pos = start + quoted.length;
|
||||
textarea.setSelectionRange(pos, pos);
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="border-t border-slate-700/50 bg-slate-900/55 p-3">
|
||||
{activeChatId && replyToByChat[activeChatId] ? (
|
||||
@@ -950,6 +975,12 @@ export function MessageComposer() {
|
||||
<button className="rounded px-2 py-1 text-xs hover:bg-slate-800" onClick={() => insertFormatting("`", "`")} type="button" title="Monospace">
|
||||
M
|
||||
</button>
|
||||
<button className="rounded px-2 py-1 text-xs hover:bg-slate-800" onClick={() => insertFormatting("```\n", "\n```", "code")} type="button" title="Code block">
|
||||
{"</>"}
|
||||
</button>
|
||||
<button className="rounded px-2 py-1 text-xs hover:bg-slate-800" onClick={insertQuoteBlock} type="button" title="Quote">
|
||||
❝
|
||||
</button>
|
||||
<button className="rounded px-2 py-1 text-xs hover:bg-slate-800" onClick={insertLink} type="button" title="Link">
|
||||
🔗
|
||||
</button>
|
||||
|
||||
@@ -108,5 +108,28 @@ function parseInline(text: string): string {
|
||||
}
|
||||
|
||||
export function formatMessageHtml(text: string): string {
|
||||
return parseInline(text);
|
||||
const blocks: string[] = [];
|
||||
const parts = text.split(/```/);
|
||||
for (let i = 0; i < parts.length; i += 1) {
|
||||
const part = parts[i];
|
||||
const isCode = i % 2 === 1;
|
||||
if (isCode) {
|
||||
const value = escapeHtml(part.replace(/^\n+|\n+$/g, ""));
|
||||
blocks.push(`<pre class="my-1 overflow-x-auto rounded-lg bg-slate-900/90 px-2 py-1.5 text-[12px]"><code>${value}</code></pre>`);
|
||||
continue;
|
||||
}
|
||||
const lines = part.split("\n");
|
||||
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
||||
const line = lines[lineIndex];
|
||||
if (line.startsWith("> ")) {
|
||||
blocks.push(`<blockquote class="my-1 border-l-2 border-sky-400/80 bg-slate-800/60 px-2 py-1 text-sm">${parseInline(line.slice(2))}</blockquote>`);
|
||||
} else {
|
||||
blocks.push(parseInline(line));
|
||||
}
|
||||
if (lineIndex < lines.length - 1) {
|
||||
blocks.push("<br/>");
|
||||
}
|
||||
}
|
||||
}
|
||||
return blocks.join("");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user