Revert "Chat: Add markdown preview toggle with client-side rendering and user preference"
This reverts commit ffe9f30c0a.
jocadbz
parent
ffe9f30c0a
commit
9c959f6412
|
|
@ -223,21 +223,12 @@ func ChatHandler(app *App) http.HandlerFunc {
|
|||
}
|
||||
allUsernamesJSON, _ := json.Marshal(allUsernames)
|
||||
|
||||
// Get user preferences for markdown preview default
|
||||
prefs, err := models.GetUserPreferences(app.DB, userID)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching user preferences: %v", err)
|
||||
// Create default if not found
|
||||
prefs, _ = models.CreateDefaultPreferences(app.DB, userID)
|
||||
}
|
||||
|
||||
data := struct {
|
||||
PageData
|
||||
Board models.Board
|
||||
Messages []models.ChatMessage
|
||||
AllUsernames template.JS
|
||||
CurrentUsername string
|
||||
MarkdownPreviewDefault string
|
||||
Board models.Board
|
||||
Messages []models.ChatMessage
|
||||
AllUsernames template.JS
|
||||
CurrentUsername string
|
||||
}{
|
||||
PageData: PageData{
|
||||
Title: "ThreadR Chat - " + board.Name,
|
||||
|
|
@ -248,11 +239,10 @@ func ChatHandler(app *App) http.HandlerFunc {
|
|||
StaticPath: app.Config.ThreadrDir + "/static",
|
||||
CurrentURL: r.URL.Path,
|
||||
},
|
||||
Board: *board,
|
||||
Messages: messages,
|
||||
AllUsernames: template.JS(allUsernamesJSON),
|
||||
CurrentUsername: currentUsername,
|
||||
MarkdownPreviewDefault: prefs.MarkdownPreviewDefault,
|
||||
Board: *board,
|
||||
Messages: messages,
|
||||
AllUsernames: template.JS(allUsernamesJSON),
|
||||
CurrentUsername: currentUsername,
|
||||
}
|
||||
if err := app.Tmpl.ExecuteTemplate(w, "chat", data); err != nil {
|
||||
log.Printf("Error executing template in ChatHandler: %v", err)
|
||||
|
|
|
|||
115
static/app.js
115
static/app.js
|
|
@ -371,118 +371,3 @@ function showDraftIndicator(content, timestamp, onRestore, onDiscard) {
|
|||
|
||||
return indicator;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Markdown Preview Functions
|
||||
// ============================================
|
||||
|
||||
// Escape HTML to prevent XSS
|
||||
function escapeHTML(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Process inline markdown (bold, italic, code, mentions)
|
||||
function processInlineMarkdown(line) {
|
||||
// Inline code first (to avoid processing markdown inside code)
|
||||
line = line.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||
|
||||
// Bold: **text** or __text__
|
||||
line = line.replace(/\*\*([^\*]+)\*\*/g, '<strong>$1</strong>');
|
||||
line = line.replace(/__([^_]+)__/g, '<strong>$1</strong>');
|
||||
|
||||
// Italic: *text* or _text_
|
||||
line = line.replace(/\*([^\*]+)\*/g, '<em>$1</em>');
|
||||
line = line.replace(/_([^_]+)_/g, '<em>$1</em>');
|
||||
|
||||
// Mentions: @username
|
||||
line = line.replace(/@(\w+)/g, '<span class="chat-message-mention">@$1</span>');
|
||||
|
||||
return line;
|
||||
}
|
||||
|
||||
// Render markdown to HTML (matching Go implementation)
|
||||
function renderMarkdownPreview(content) {
|
||||
let html = '';
|
||||
|
||||
// Extract code blocks first and replace with placeholders
|
||||
const codeBlocks = [];
|
||||
let codeBlockCounter = 0;
|
||||
|
||||
content = content.replace(/```(\w*)\n([\s\S]*?)\n```/g, (match, lang, code) => {
|
||||
const escapedCode = escapeHTML(code);
|
||||
let renderedBlock;
|
||||
if (lang) {
|
||||
renderedBlock = `<pre><code class="language-${lang}">${escapedCode}</code></pre>`;
|
||||
} else {
|
||||
renderedBlock = `<pre><code>${escapedCode}</code></pre>`;
|
||||
}
|
||||
const placeholder = `<!--CODEBLOCK_${codeBlockCounter}-->`;
|
||||
codeBlocks[codeBlockCounter] = renderedBlock;
|
||||
codeBlockCounter++;
|
||||
return placeholder;
|
||||
});
|
||||
|
||||
// Process lines
|
||||
const lines = content.split('\n');
|
||||
let inList = false;
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmedLine = line.trim();
|
||||
|
||||
// Headers
|
||||
if (trimmedLine.startsWith('### ')) {
|
||||
if (inList) { html += '</ul>\n'; inList = false; }
|
||||
html += '<h3>' + processInlineMarkdown(trimmedLine.substring(4)) + '</h3>\n';
|
||||
continue;
|
||||
} else if (trimmedLine.startsWith('## ')) {
|
||||
if (inList) { html += '</ul>\n'; inList = false; }
|
||||
html += '<h2>' + processInlineMarkdown(trimmedLine.substring(3)) + '</h2>\n';
|
||||
continue;
|
||||
} else if (trimmedLine.startsWith('# ')) {
|
||||
if (inList) { html += '</ul>\n'; inList = false; }
|
||||
html += '<h1>' + processInlineMarkdown(trimmedLine.substring(2)) + '</h1>\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
// Lists
|
||||
if (trimmedLine.startsWith('* ') || trimmedLine.startsWith('- ')) {
|
||||
if (!inList) {
|
||||
html += '<ul>\n';
|
||||
inList = true;
|
||||
}
|
||||
const listContent = trimmedLine.substring(2);
|
||||
html += '<li>' + processInlineMarkdown(listContent) + '</li>\n';
|
||||
continue;
|
||||
}
|
||||
|
||||
// Close list if we're not in a list item anymore
|
||||
if (inList) {
|
||||
html += '</ul>\n';
|
||||
inList = false;
|
||||
}
|
||||
|
||||
// Regular paragraphs
|
||||
if (trimmedLine !== '') {
|
||||
html += '<p>' + processInlineMarkdown(trimmedLine) + '</p>\n';
|
||||
} else {
|
||||
html += '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// Close list if still open
|
||||
if (inList) {
|
||||
html += '</ul>\n';
|
||||
}
|
||||
|
||||
// Replace code block placeholders
|
||||
codeBlocks.forEach((block, index) => {
|
||||
html = html.replace(`<!--CODEBLOCK_${index}-->`, block);
|
||||
});
|
||||
|
||||
// Clean up excessive newlines
|
||||
html = html.replace(/\n{3,}/g, '\n\n');
|
||||
|
||||
return html;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -247,99 +247,6 @@
|
|||
padding: 6px 12px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.markdown-tabs {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid #001858;
|
||||
}
|
||||
.markdown-tab {
|
||||
flex: 1;
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background-color: #f3d2c1;
|
||||
color: #001858;
|
||||
font-family: monospace;
|
||||
font-size: 0.9em;
|
||||
cursor: pointer;
|
||||
border-right: 1px solid #001858;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
.markdown-tab:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
.markdown-tab:hover {
|
||||
background-color: #fef6e4;
|
||||
}
|
||||
.markdown-tab.active {
|
||||
background-color: #8bd3dd;
|
||||
color: #001858;
|
||||
font-weight: bold;
|
||||
}
|
||||
.markdown-content-container {
|
||||
position: relative;
|
||||
min-height: 50px;
|
||||
}
|
||||
.chat-input textarea,
|
||||
.markdown-preview {
|
||||
display: none;
|
||||
resize: none;
|
||||
height: 50px;
|
||||
margin-bottom: 8px;
|
||||
font-size: 0.9em;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.chat-input textarea.markdown-visible,
|
||||
.markdown-preview.markdown-visible {
|
||||
display: block;
|
||||
}
|
||||
.markdown-preview {
|
||||
border: 1px solid #001858;
|
||||
border-radius: 5px;
|
||||
padding: 8px;
|
||||
background-color: #fef6e4;
|
||||
color: #001858;
|
||||
min-height: 50px;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
font-family: monospace;
|
||||
}
|
||||
.markdown-preview h1 {
|
||||
font-size: 1.5em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.markdown-preview h2 {
|
||||
font-size: 1.3em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.markdown-preview h3 {
|
||||
font-size: 1.1em;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.markdown-preview p {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.markdown-preview ul {
|
||||
margin: 0.5em 0;
|
||||
padding-left: 2em;
|
||||
}
|
||||
.markdown-preview code {
|
||||
background-color: #f3d2c1;
|
||||
padding: 2px 4px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.markdown-preview pre {
|
||||
background-color: #f3d2c1;
|
||||
padding: 8px;
|
||||
border-radius: 5px;
|
||||
overflow-x: auto;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
.markdown-preview pre code {
|
||||
background-color: transparent;
|
||||
padding: 0;
|
||||
}
|
||||
.post-actions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
|
|
@ -503,29 +410,6 @@
|
|||
border-color: #f582ae;
|
||||
}
|
||||
}
|
||||
.markdown-tab {
|
||||
background-color: #555;
|
||||
color: #fef6e4;
|
||||
border-color: #fef6e4;
|
||||
}
|
||||
.markdown-tab:hover {
|
||||
background-color: #666;
|
||||
}
|
||||
.markdown-tab.active {
|
||||
background-color: #8bd3dd;
|
||||
color: #001858;
|
||||
}
|
||||
.markdown-preview {
|
||||
background-color: #333;
|
||||
color: #fef6e4;
|
||||
border-color: #fef6e4;
|
||||
}
|
||||
.markdown-preview code {
|
||||
background-color: #555;
|
||||
}
|
||||
.markdown-preview pre {
|
||||
background-color: #555;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
|
@ -582,14 +466,7 @@
|
|||
<span id="reply-username">Replying to </span>
|
||||
<button onclick="cancelReply()">X</button>
|
||||
</div>
|
||||
<div class="markdown-tabs">
|
||||
<button class="markdown-tab active" data-tab="edit" onclick="switchMarkdownTab('edit')">Edit</button>
|
||||
<button class="markdown-tab" data-tab="preview" onclick="switchMarkdownTab('preview')">Preview</button>
|
||||
</div>
|
||||
<div class="markdown-content-container">
|
||||
<textarea id="chat-input-text" placeholder="Type a message..." class="markdown-visible"></textarea>
|
||||
<div id="chat-preview" class="markdown-preview"></div>
|
||||
</div>
|
||||
<textarea id="chat-input-text" placeholder="Type a message..."></textarea>
|
||||
<button onclick="sendMessage()">Send</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -952,57 +829,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
// Markdown preview functionality
|
||||
let currentTab = '{{.MarkdownPreviewDefault}}' || 'edit';
|
||||
let previewUpdateTimeout;
|
||||
|
||||
function switchMarkdownTab(tab) {
|
||||
currentTab = tab;
|
||||
const textarea = document.getElementById('chat-input-text');
|
||||
const preview = document.getElementById('chat-preview');
|
||||
const tabs = document.querySelectorAll('.markdown-tab');
|
||||
|
||||
// Update tab styling
|
||||
tabs.forEach(t => {
|
||||
if (t.dataset.tab === tab) {
|
||||
t.classList.add('active');
|
||||
} else {
|
||||
t.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Show/hide content
|
||||
if (tab === 'edit') {
|
||||
textarea.classList.add('markdown-visible');
|
||||
preview.classList.remove('markdown-visible');
|
||||
textarea.focus();
|
||||
} else {
|
||||
textarea.classList.remove('markdown-visible');
|
||||
preview.classList.add('markdown-visible');
|
||||
updatePreview();
|
||||
}
|
||||
}
|
||||
|
||||
function updatePreview() {
|
||||
const textarea = document.getElementById('chat-input-text');
|
||||
const preview = document.getElementById('chat-preview');
|
||||
const content = textarea.value;
|
||||
|
||||
if (content.trim() === '') {
|
||||
preview.innerHTML = '<p style="opacity: 0.5; font-style: italic;">Nothing to preview. Type some markdown in the Edit tab.</p>';
|
||||
} else {
|
||||
preview.innerHTML = renderMarkdownPreview(content);
|
||||
}
|
||||
}
|
||||
|
||||
// Update preview on input (debounced)
|
||||
document.getElementById('chat-input-text').addEventListener('input', () => {
|
||||
if (currentTab === 'preview') {
|
||||
clearTimeout(previewUpdateTimeout);
|
||||
previewUpdateTimeout = setTimeout(updatePreview, 300);
|
||||
}
|
||||
});
|
||||
|
||||
window.onload = function() {
|
||||
connectWebSocket();
|
||||
const messagesContainer = document.getElementById('chat-messages');
|
||||
|
|
@ -1080,9 +906,6 @@
|
|||
originalSendMessage();
|
||||
};
|
||||
|
||||
// Initialize markdown preview tab based on user preference
|
||||
switchMarkdownTab(currentTab);
|
||||
|
||||
// Initial check for scroll position
|
||||
checkScrollPosition();
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue