final v0.8
This commit is contained in:
parent
afb642ce41
commit
35c2bad3b7
@ -623,6 +623,10 @@ body {
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font-size: 1em;
|
||||
resize: none;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
/* Members Sidebar */
|
||||
@ -1257,3 +1261,76 @@ body {
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Markdown Styles */
|
||||
.message-text code {
|
||||
background-color: #1e1f22;
|
||||
padding: 0.2rem 0.4rem;
|
||||
border-radius: 4px;
|
||||
font-family: 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.85em;
|
||||
color: #dbdee1;
|
||||
}
|
||||
|
||||
.message-text pre.code-block {
|
||||
background-color: #1e1f22;
|
||||
padding: 1rem;
|
||||
border-radius: 4px;
|
||||
margin: 0.5rem 0;
|
||||
font-family: 'Consolas', 'Monaco', 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
font-size: 0.85em;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.message-text blockquote {
|
||||
border-left: 4px solid #4e5058;
|
||||
padding-left: 1rem;
|
||||
margin: 0.5rem 0;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.message-text .spoiler {
|
||||
background-color: #1e1f22;
|
||||
color: transparent !important;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.message-text .spoiler:hover {
|
||||
background-color: #2b2d31;
|
||||
}
|
||||
|
||||
.message-text .spoiler.revealed {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
color: inherit !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.message-text h1 { font-size: 1.5em; font-weight: 700; margin: 4px 0 2px 0; color: #fff; }
|
||||
.message-text h2 { font-size: 1.25em; font-weight: 600; margin: 3px 0 1px 0; color: #fff; }
|
||||
.message-text h3 { font-size: 1.1em; font-weight: 600; margin: 2px 0 0 0; color: #fff; }
|
||||
|
||||
[data-theme="light"] .message-text code {
|
||||
background-color: #ebedef;
|
||||
color: #313338;
|
||||
}
|
||||
|
||||
[data-theme="light"] .message-text pre.code-block {
|
||||
background-color: #ebedef;
|
||||
color: #313338;
|
||||
}
|
||||
|
||||
[data-theme="light"] .message-text .spoiler {
|
||||
background-color: #dbdee1;
|
||||
}
|
||||
|
||||
[data-theme="light"] .message-text .spoiler.revealed {
|
||||
background-color: #f2f3f5;
|
||||
}
|
||||
|
||||
[data-theme="light"] .message-text h1,
|
||||
[data-theme="light"] .message-text h2,
|
||||
[data-theme="light"] .message-text h3 {
|
||||
color: #313338;
|
||||
}
|
||||
|
||||
@ -402,6 +402,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (chatInput) {
|
||||
chatInput.value += emoji;
|
||||
chatInput.focus();
|
||||
chatInput.dispatchEvent(new Event('input'));
|
||||
}
|
||||
}, { keepOpen: true, width: "900px", height: "500px" });
|
||||
return;
|
||||
@ -495,8 +496,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
} else if (msg.type === 'reaction') {
|
||||
updateReactionUI(msg.message_id, msg.reactions);
|
||||
} else if (msg.type === 'message_edit') {
|
||||
const el = document.querySelector(`.message-item[data-id="${msg.message_id}"] .message-text`);
|
||||
if (el) el.innerHTML = msg.content.replace(/\n/g, '<br>');
|
||||
const item = document.querySelector(`.message-item[data-id="${msg.message_id}"]`);
|
||||
if (item) {
|
||||
item.dataset.rawContent = msg.content;
|
||||
const el = item.querySelector('.message-text');
|
||||
if (el) el.innerHTML = parseCustomEmotes(msg.content);
|
||||
}
|
||||
} else if (msg.type === 'message_delete') {
|
||||
document.querySelector(`.message-item[data-id="${msg.message_id}"]`)?.remove();
|
||||
} else if (msg.type === 'presence') {
|
||||
@ -549,7 +554,22 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
chatInput?.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
chatForm?.dispatchEvent(new Event('submit', { cancelable: true }));
|
||||
}
|
||||
});
|
||||
|
||||
chatInput?.addEventListener('input', () => {
|
||||
chatInput.style.height = 'auto';
|
||||
chatInput.style.height = Math.min(chatInput.scrollHeight, 200) + 'px';
|
||||
if (chatInput.scrollHeight > 200) {
|
||||
chatInput.style.overflowY = 'auto';
|
||||
} else {
|
||||
chatInput.style.overflowY = 'hidden';
|
||||
}
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'typing',
|
||||
@ -567,6 +587,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!content && !file) return;
|
||||
|
||||
chatInput.value = '';
|
||||
chatInput.style.height = '24px';
|
||||
chatInput.style.overflowY = 'hidden';
|
||||
const formData = new FormData();
|
||||
formData.append('content', content);
|
||||
formData.append('channel_id', currentChannel);
|
||||
@ -732,19 +754,32 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const msgId = editBtn.dataset.id;
|
||||
const msgItem = editBtn.closest('.message-item');
|
||||
const textEl = msgItem.querySelector('.message-text');
|
||||
const originalContent = textEl.innerText;
|
||||
const originalContent = msgItem.dataset.rawContent || textEl.innerText;
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
const input = document.createElement('textarea');
|
||||
input.className = 'form-control bg-dark text-white';
|
||||
input.style.resize = 'none';
|
||||
input.style.overflowY = 'hidden';
|
||||
input.rows = 1;
|
||||
input.value = originalContent;
|
||||
|
||||
textEl.innerHTML = '';
|
||||
textEl.appendChild(input);
|
||||
|
||||
const resizeInput = () => {
|
||||
input.style.height = 'auto';
|
||||
input.style.height = Math.min(input.scrollHeight, 200) + 'px';
|
||||
input.style.overflowY = input.scrollHeight > 200 ? 'auto' : 'hidden';
|
||||
};
|
||||
|
||||
input.addEventListener('input', resizeInput);
|
||||
setTimeout(resizeInput, 0);
|
||||
input.focus();
|
||||
input.setSelectionRange(input.value.length, input.value.length);
|
||||
|
||||
input.onkeydown = async (ev) => {
|
||||
if (ev.key === 'Enter') {
|
||||
if (ev.key === 'Enter' && !ev.shiftKey) {
|
||||
ev.preventDefault();
|
||||
const newContent = input.value.trim();
|
||||
if (newContent && newContent !== originalContent) {
|
||||
const resp = await fetch('api_v1_messages.php', {
|
||||
@ -753,14 +788,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
body: JSON.stringify({ id: msgId, content: newContent })
|
||||
});
|
||||
if ((await resp.json()).success) {
|
||||
textEl.innerHTML = newContent.replace(/\n/g, '<br>');
|
||||
textEl.innerHTML = parseCustomEmotes(newContent);
|
||||
msgItem.dataset.rawContent = newContent;
|
||||
ws?.send(JSON.stringify({ type: 'message_edit', message_id: msgId, content: newContent }));
|
||||
}
|
||||
} else {
|
||||
textEl.innerHTML = originalContent.replace(/\n/g, '<br>');
|
||||
textEl.innerHTML = parseCustomEmotes(originalContent);
|
||||
}
|
||||
} else if (ev.key === 'Escape') {
|
||||
textEl.innerHTML = originalContent.replace(/\n/g, '<br>');
|
||||
textEl.innerHTML = parseCustomEmotes(originalContent);
|
||||
}
|
||||
};
|
||||
return;
|
||||
@ -814,6 +850,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
data.messages.forEach(msg => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'message-item p-2 border-bottom border-secondary';
|
||||
div.dataset.id = msg.id;
|
||||
div.dataset.rawContent = msg.content;
|
||||
div.style.backgroundColor = 'transparent';
|
||||
const authorStyle = msg.role_color ? `color: ${msg.role_color};` : '';
|
||||
div.innerHTML = `
|
||||
@ -826,7 +864,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
<span class="message-time">${msg.time}</span>
|
||||
</div>
|
||||
<div class="message-text" style="font-size: 0.9em;">
|
||||
${escapeHTML(msg.content).replace(/\n/g, '<br>')}
|
||||
${parseCustomEmotes(msg.content)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -966,8 +1004,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
} else {
|
||||
item.innerHTML = `
|
||||
<div class="flex-grow-1">
|
||||
<div class="search-result-author">${res.username}</div>
|
||||
<div class="search-result-text">${res.content}</div>
|
||||
<div class="search-result-author">${escapeHTML(res.username)}</div>
|
||||
<div class="search-result-text">${parseCustomEmotes(res.content)}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@ -2375,15 +2413,93 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
// User Settings - Save handled in index.php
|
||||
|
||||
function escapeHTML(str) {
|
||||
if (!str) return "";
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
function parseMarkdown(text) {
|
||||
if (!text) return "";
|
||||
|
||||
// Escape HTML first
|
||||
let html = escapeHTML(text);
|
||||
|
||||
// Code blocks: ```language\ncontent```
|
||||
const codeBlocks = [];
|
||||
html = html.replace(/```(?:(\w+)\n)?([\s\S]*?)```/g, (match, lang, content) => {
|
||||
const placeholder = `__CODE_BLOCK_${codeBlocks.length}__`;
|
||||
codeBlocks.push(`<pre class="code-block"><code class="language-${lang || 'text'}">${content}</code></pre>`);
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Inline code: `content`
|
||||
const inlineCodes = [];
|
||||
html = html.replace(/`([^`\n]+)`/g, (match, content) => {
|
||||
const placeholder = `__INLINE_CODE_${inlineCodes.length}__`;
|
||||
inlineCodes.push(`<code>${content}</code>`);
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Bold: **text**
|
||||
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
||||
|
||||
// Italics: *text* or _text_
|
||||
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
||||
html = html.replace(/_([^_]+)_/g, '<em>$1</em>');
|
||||
|
||||
// Underline: __text__
|
||||
html = html.replace(/__([^_]+)__/g, '<u>$1</u>');
|
||||
|
||||
// Strikethrough: ~~text~~
|
||||
html = html.replace(/~~([^~]+)~~/g, '<del>$1</del>');
|
||||
|
||||
// Spoiler: ||text||
|
||||
html = html.replace(/\|\|([^|]+)\|\|/g, '<span class="spoiler" onclick="this.classList.toggle(\'revealed\')">$1</span>');
|
||||
|
||||
// Headers: # H1, ## H2, ### H3 (must be at start of line)
|
||||
html = html.replace(/^# (.*$)/gm, '<h1>$1</h1>');
|
||||
html = html.replace(/^## (.*$)/gm, '<h2>$1</h2>');
|
||||
html = html.replace(/^### (.*$)/gm, '<h3>$1</h3>');
|
||||
|
||||
// Subtext: -# text (must be at start of line)
|
||||
html = html.replace(/^-# (.*$)/gm, '<small class="text-muted d-block" style="font-size: 0.8em;">$1</small>');
|
||||
|
||||
// Blockquotes: > text or >>> text (must be at start of line)
|
||||
html = html.replace(/^> (.*$)/gm, '<blockquote>$1</blockquote>');
|
||||
html = html.replace(/^>>> ([\s\S]*$)/g, '<blockquote>$1</blockquote>');
|
||||
|
||||
// Hyperlinks: [text](url)
|
||||
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
|
||||
|
||||
// Pure links: <url>
|
||||
html = html.replace(/<(https?:\/\/[^&]+)>/g, '<a href="$1" target="_blank">$1</a>');
|
||||
|
||||
// Newlines to <br> (only those not inside placeholders)
|
||||
html = html.replace(/\n/g, '<br>');
|
||||
|
||||
// Remove extra space around headers and blockquotes added by nl2br
|
||||
html = html.replace(/(<br>)\s*(<h1>|<h2>|<h3>|<blockquote>)/gi, '$2');
|
||||
html = html.replace(/(<\/h1>|<\/h2>|<\/h3>|<\/blockquote>)\s*(<br>)/gi, '$1');
|
||||
|
||||
// Re-insert inline code
|
||||
inlineCodes.forEach((code, i) => {
|
||||
html = html.replace(`__INLINE_CODE_${i}__`, code);
|
||||
});
|
||||
|
||||
// Re-insert code blocks
|
||||
codeBlocks.forEach((block, i) => {
|
||||
html = html.replace(`__CODE_BLOCK_${i}__`, block);
|
||||
});
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function parseCustomEmotes(text) {
|
||||
let parsed = escapeHTML(text);
|
||||
let parsed = parseMarkdown(text);
|
||||
(window.CUSTOM_EMOTES_CACHE || []).forEach(emote => {
|
||||
const imgHtml = `<img src="${emote.path}" alt="${emote.name}" title="${emote.code}" style="width: 24px; height: 24px; vertical-align: middle; object-fit: contain;">`;
|
||||
// Only replace if it's not inside a tag attribute or code block (simplified)
|
||||
parsed = parsed.split(emote.code).join(imgHtml);
|
||||
});
|
||||
return parsed;
|
||||
@ -2409,6 +2525,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'message-item';
|
||||
div.dataset.id = msg.id;
|
||||
div.dataset.rawContent = msg.content;
|
||||
|
||||
if (parseInt(msg.id) > lastMessageId) {
|
||||
lastMessageId = parseInt(msg.id);
|
||||
@ -2483,7 +2600,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
<span class="message-timestamp">${msg.timestamp || 'Just now'}</span>
|
||||
${pinnedBadge}
|
||||
</div>
|
||||
<div class="message-text">${contentWithMentions.replace(/\n/g, '<br>')}</div>
|
||||
<div class="message-text">${contentWithMentions}</div>
|
||||
${attachmentHtml}
|
||||
${embedHtml}
|
||||
<div class="message-reactions mt-1" data-message-id="${msg.id}"></div>
|
||||
|
||||
126
index.php
126
index.php
@ -28,8 +28,88 @@ function renderRoleIcon($icon, $size = '14px') {
|
||||
}
|
||||
}
|
||||
|
||||
// Helper to parse markdown in content
|
||||
function parse_markdown($text) {
|
||||
if (empty($text)) return "";
|
||||
|
||||
// First escape HTML
|
||||
$html = htmlspecialchars($text);
|
||||
|
||||
// Code blocks: ```language\ncontent```
|
||||
$code_blocks = [];
|
||||
$html = preg_replace_callback('/```(?:(\w+)\n)?([\s\S]*?)```/', function($matches) use (&$code_blocks) {
|
||||
$lang = $matches[1] ?? 'text';
|
||||
$content = $matches[2];
|
||||
$placeholder = "__CODE_BLOCK_" . count($code_blocks) . "__";
|
||||
$code_blocks[] = '<pre class="code-block"><code class="language-' . htmlspecialchars($lang) . '">' . $content . '</code></pre>';
|
||||
return $placeholder;
|
||||
}, $html);
|
||||
|
||||
// Inline code: `content`
|
||||
$inline_codes = [];
|
||||
$html = preg_replace_callback('/`([^`\n]+)`/', function($matches) use (&$inline_codes) {
|
||||
$content = $matches[1];
|
||||
$placeholder = "__INLINE_CODE_" . count($inline_codes) . "__";
|
||||
$inline_codes[] = '<code>' . $content . '</code>';
|
||||
return $placeholder;
|
||||
}, $html);
|
||||
|
||||
// Bold: **text**
|
||||
$html = preg_replace('/\*\*([^*]+)\*\*/', '<strong>$1</strong>', $html);
|
||||
|
||||
// Italics: *text* or _text_
|
||||
$html = preg_replace('/\*([^*]+)\*/', '<em>$1</em>', $html);
|
||||
$html = preg_replace('/_([^_]+)_/', '<em>$1</em>', $html);
|
||||
|
||||
// Underline: __text__
|
||||
$html = preg_replace('/__([^_]+)__/', '<u>$1</u>', $html);
|
||||
|
||||
// Strikethrough: ~~text~~
|
||||
$html = preg_replace('/~~([^~]+)~~/', '<del>$1</del>', $html);
|
||||
|
||||
// Spoiler: ||text||
|
||||
$html = preg_replace('/\|\|([^|]+)\|\|/', '<span class="spoiler" onclick="this.classList.toggle(\'revealed\')">$1</span>', $html);
|
||||
|
||||
// Headers: # H1, ## H2, ### H3 (must be at start of line)
|
||||
$html = preg_replace('/^# (.*$)/m', '<h1>$1</h1>', $html);
|
||||
$html = preg_replace('/^## (.*$)/m', '<h2>$1</h2>', $html);
|
||||
$html = preg_replace('/^### (.*$)/m', '<h3>$1</h3>', $html);
|
||||
|
||||
// Subtext: -# text (must be at start of line)
|
||||
$html = preg_replace('/^-# (.*$)/m', '<small class="text-muted d-block" style="font-size: 0.8em;">$1</small>', $html);
|
||||
|
||||
// Blockquotes: > text or >>> text
|
||||
$html = preg_replace('/^> (.*$)/m', '<blockquote>$1</blockquote>', $html);
|
||||
$html = preg_replace('/^>>> ([\s\S]*$)/', '<blockquote>$1</blockquote>', $html);
|
||||
|
||||
// Hyperlinks: [text](url)
|
||||
$html = preg_replace('/\[([^\]]+)\]\(([^)]+)\)/', '<a href="$2" target="_blank">$1</a>', $html);
|
||||
|
||||
// Pure links: <url>
|
||||
$html = preg_replace('/<(https?:\/\/[^&]+)>/', '<a href="$1" target="_blank">$1</a>', $html);
|
||||
|
||||
// Newlines to <br> (only those not inside placeholders)
|
||||
$html = nl2br($html);
|
||||
|
||||
// Remove extra space around headers and blockquotes added by nl2br
|
||||
$html = preg_replace('/(<br\s*\/?>\s*)\s*(<h[1-3]>|<blockquote>)/i', '$2', $html);
|
||||
$html = preg_replace('/(<\/h[1-3]>|<\/blockquote>)\s*(<br\s*\/?>\s*)/i', '$1', $html);
|
||||
|
||||
// Re-insert inline code
|
||||
foreach ($inline_codes as $i => $code) {
|
||||
$html = str_replace("__INLINE_CODE_$i" . "__", $code, $html);
|
||||
}
|
||||
|
||||
// Re-insert code blocks
|
||||
foreach ($code_blocks as $i => $block) {
|
||||
$html = str_replace("__CODE_BLOCK_$i" . "__", $block, $html);
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
// Helper to parse emotes in content
|
||||
function parse_emotes($content) {
|
||||
function parse_emotes($content, $username_to_mention = null) {
|
||||
static $custom_emotes_cache;
|
||||
if ($custom_emotes_cache === null) {
|
||||
try {
|
||||
@ -39,7 +119,14 @@ function parse_emotes($content) {
|
||||
}
|
||||
}
|
||||
|
||||
$result = htmlspecialchars($content);
|
||||
$result = parse_markdown($content);
|
||||
|
||||
// Parse mentions if username provided
|
||||
if ($username_to_mention) {
|
||||
$mention_pattern = '/@' . preg_quote($username_to_mention, '/') . '\b/';
|
||||
$result = preg_replace($mention_pattern, '<span class="mention">@' . htmlspecialchars($username_to_mention) . '</span>', $result);
|
||||
}
|
||||
|
||||
foreach ($custom_emotes_cache as $ce) {
|
||||
$emote_html = '<img src="' . htmlspecialchars($ce['path']) . '" alt="' . htmlspecialchars($ce['name']) . '" title="' . htmlspecialchars($ce['code']) . '" style="width: 24px; height: 24px; vertical-align: middle; object-fit: contain;">';
|
||||
$result = str_replace($ce['code'], $emote_html, $result);
|
||||
@ -590,7 +677,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
$is_solution = ($active_thread['solution_message_id'] == $m['id']);
|
||||
?>
|
||||
<!-- Message rendering code (reused) -->
|
||||
<div class="message-item <?php echo $is_mentioned ? 'mentioned' : ''; ?> <?php echo $is_solution ? 'is-solution' : ''; ?>" data-id="<?php echo $m['id']; ?>">
|
||||
<div class="message-item <?php echo $is_mentioned ? 'mentioned' : ''; ?> <?php echo $is_solution ? 'is-solution' : ''; ?>" data-id="<?php echo $m['id']; ?>" data-raw-content="<?php echo htmlspecialchars($m['content']); ?>">
|
||||
<div class="message-avatar" style="<?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>"></div>
|
||||
<div class="message-content">
|
||||
<div class="message-author" style="<?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||||
@ -606,10 +693,18 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20 6 9 17 4 12"></polyline></svg>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($m['user_id'] == $current_user_id): ?>
|
||||
<span class="action-btn edit" title="Edit" data-id="<?php echo $m['id']; ?>">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
|
||||
</span>
|
||||
<span class="action-btn delete" title="Delete" data-id="<?php echo $m['id']; ?>">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-text">
|
||||
<?php echo nl2br(htmlspecialchars($m['content'])); ?>
|
||||
<?php echo parse_emotes($m['content'], $user['username']); ?>
|
||||
</div>
|
||||
<div class="message-reactions mt-1" data-message-id="<?php echo $m['id']; ?>">
|
||||
<?php
|
||||
@ -639,7 +734,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<div class="rule-item mb-3 p-3 rounded bg-dark border-start border-4 border-primary d-flex justify-content-between align-items-center" data-id="<?php echo $rule['id']; ?>">
|
||||
<div class="rule-content flex-grow-1">
|
||||
<span class="rule-number fw-bold me-2"><?php echo $i++; ?>.</span>
|
||||
<?php echo nl2br(htmlspecialchars($rule['content'])); ?>
|
||||
<?php echo parse_emotes($rule['content']); ?>
|
||||
</div>
|
||||
<?php if($can_manage_channels): ?>
|
||||
<div class="rule-actions ms-3 d-flex gap-2">
|
||||
@ -814,7 +909,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
$mention_pattern = '/@' . preg_quote($user['username'], '/') . '\b/';
|
||||
$is_mentioned = preg_match($mention_pattern, $m['content']);
|
||||
?>
|
||||
<div class="message-item <?php echo $is_mentioned ? 'mentioned' : ''; ?> <?php echo $m['is_pinned'] ? 'pinned' : ''; ?> <?php echo $channel_type === 'announcement' ? 'announcement-style' : ''; ?>" data-id="<?php echo $m['id']; ?>">
|
||||
<div class="message-item <?php echo $is_mentioned ? 'mentioned' : ''; ?> <?php echo $m['is_pinned'] ? 'pinned' : ''; ?> <?php echo $channel_type === 'announcement' ? 'announcement-style' : ''; ?>" data-id="<?php echo $m['id']; ?>" data-raw-content="<?php echo htmlspecialchars($m['content']); ?>">
|
||||
<div class="message-avatar" style="<?php echo $m['avatar_url'] ? "background-image: url('{$m['avatar_url']}');" : ""; ?>"></div>
|
||||
<div class="message-content">
|
||||
<div class="message-header">
|
||||
@ -831,22 +926,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="message-text">
|
||||
<?php
|
||||
$msg_content = htmlspecialchars($m['content']);
|
||||
$msg_content = preg_replace($mention_pattern, '<span class="mention">@' . htmlspecialchars($user['username']) . '</span>', $msg_content);
|
||||
|
||||
// Custom Emotes parsing
|
||||
static $custom_emotes_cache;
|
||||
if ($custom_emotes_cache === null) {
|
||||
$custom_emotes_cache = db()->query("SELECT name, path, code FROM custom_emotes")->fetchAll();
|
||||
}
|
||||
foreach ($custom_emotes_cache as $ce) {
|
||||
$emote_html = '<img src="' . htmlspecialchars($ce['path']) . '" alt="' . htmlspecialchars($ce['name']) . '" title="' . htmlspecialchars($ce['code']) . '" style="width: 24px; height: 24px; vertical-align: middle; object-fit: contain;">';
|
||||
$msg_content = str_replace($ce['code'], $emote_html, $msg_content);
|
||||
}
|
||||
|
||||
echo nl2br($msg_content);
|
||||
?>
|
||||
<?php echo parse_emotes($m['content'], $user['username']); ?>
|
||||
<?php if ($m['attachment_url']): ?>
|
||||
<div class="message-attachment mt-2">
|
||||
<?php
|
||||
@ -976,7 +1056,7 @@ $emote_html = '<img src="' . htmlspecialchars($ce['path']) . '" alt="' . htmlspe
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><line x1="8" y1="8" x2="16" y2="16"></line><line x1="16" y1="8" x2="8" y2="16"></line></svg>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<input type="text" id="chat-input" class="chat-input" placeholder="Message #<?php echo htmlspecialchars($current_channel_name); ?>" autocomplete="off">
|
||||
<textarea id="chat-input" class="chat-input" placeholder="Message #<?php echo htmlspecialchars($current_channel_name); ?>" autocomplete="off" rows="1"></textarea>
|
||||
<button type="button" class="btn border-0 text-muted p-2" id="chat-emoji-btn" title="Emoji Picker">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M8 14s1.5 2 4 2 4-2 4-2"></path><line x1="9" y1="9" x2="9.01" y2="9"></line><line x1="15" y1="9" x2="15.01" y2="9"></line></svg>
|
||||
</button>
|
||||
|
||||
17
requests.log
17
requests.log
@ -424,3 +424,20 @@
|
||||
2026-02-17 00:34:40 - GET /index.php?server_id=1 - POST: []
|
||||
2026-02-17 00:34:45 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||
2026-02-17 00:35:23 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||
2026-02-17 00:43:23 - - POST: []
|
||||
2026-02-17 00:44:01 - GET /index.php - POST: []
|
||||
2026-02-17 00:44:15 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-17 00:45:12 - GET /index.php?server_id=1&channel_id=1 - POST: []
|
||||
2026-02-17 00:45:14 - GET /index.php?server_id=1&channel_id=20 - POST: []
|
||||
2026-02-17 00:45:21 - GET /index.php?server_id=1&channel_id=15 - POST: []
|
||||
2026-02-17 00:45:23 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||
2026-02-17 00:48:04 - GET / - POST: []
|
||||
2026-02-17 00:48:39 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-17 00:48:50 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||
2026-02-17 00:57:46 - GET / - POST: []
|
||||
2026-02-17 00:58:08 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-17 00:58:11 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||
2026-02-17 00:58:14 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||
2026-02-17 01:02:05 - GET / - POST: []
|
||||
2026-02-17 01:02:20 - GET /?fl_project=38443 - POST: []
|
||||
2026-02-17 01:02:36 - GET /index.php?server_id=1&channel_id=6 - POST: []
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user