feat: open links in new tab with security attributes
- Add target="_blank" and rel="noopener noreferrer" to all links - Prevents tabnabbing and improves UX - Updated layout margins (1400px max-width, 10px padding, 95vh height) - Fixed markdown rendering (assistant HTML preserved without <br> conversion)
This commit is contained in:
parent
b7f0a567da
commit
df4f73a36d
3 changed files with 60 additions and 12 deletions
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
|
@ -301,7 +302,24 @@ func renderMarkdown(input string) string {
|
|||
logger.ErrorCF("http", "renderMarkdown error", map[string]any{"error": err.Error()})
|
||||
return input // fallback to plain text on error
|
||||
}
|
||||
return buf.String()
|
||||
|
||||
// Add target="_blank" to all links for security and UX
|
||||
html := buf.String()
|
||||
|
||||
// Pattern to match <a href="..."> links
|
||||
// This regex adds target="_blank" and rel="noopener noreferrer" to all <a> tags
|
||||
// that don't already have a target attribute
|
||||
re := regexp.MustCompile(`<a\s+([^>]*?)href="([^"]*?)"([^>]*?)>`)
|
||||
html = re.ReplaceAllStringFunc(html, func(match string) string {
|
||||
// Check if target attribute already exists
|
||||
if strings.Contains(match, `target=`) {
|
||||
return match
|
||||
}
|
||||
// Insert target and rel attributes before closing >
|
||||
return strings.Replace(match, `>`, ` target="_blank" rel="noopener noreferrer">`, 1)
|
||||
})
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
|
|
|
|||
|
|
@ -17,18 +17,18 @@
|
|||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1200px;
|
||||
max-width: 1400px;
|
||||
background: #0f0f23;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 90vh;
|
||||
height: 95vh;
|
||||
min-height: 600px;
|
||||
}
|
||||
.header {
|
||||
|
|
@ -83,6 +83,7 @@
|
|||
border-radius: 16px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
white-space: normal; /* Evita que los \n del HTML se rendericen como espacios */
|
||||
}
|
||||
.message.user {
|
||||
align-self: flex-end;
|
||||
|
|
@ -96,6 +97,31 @@
|
|||
color: #e2e8f0;
|
||||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
/* Estilos para tablas en HTTP */
|
||||
.message.assistant table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
margin: 1em 0;
|
||||
border: 1px solid #334155;
|
||||
}
|
||||
.message.assistant th,
|
||||
.message.assistant td {
|
||||
border: 1px solid #475569;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
white-space: nowrap; /* Evita saltos dentro de celdas */
|
||||
background: #0f172a;
|
||||
}
|
||||
.message.assistant th {
|
||||
background: #1e293b;
|
||||
font-weight: 600;
|
||||
}
|
||||
.message.assistant tr:nth-child(even) td {
|
||||
background: #162032;
|
||||
}
|
||||
.message.assistant pre {
|
||||
background: #0f172a;
|
||||
padding: 12px;
|
||||
|
|
@ -253,11 +279,15 @@
|
|||
function addMessage(content, type) {
|
||||
const div = document.createElement('div');
|
||||
div.className = `message ${type}`;
|
||||
// Convert newlines to <br> for proper rendering in HTML
|
||||
// For user messages, we need to preserve line breaks from textarea
|
||||
let processedContent = content.replace(/\n/g, '<br>');
|
||||
// Also handle double newlines as paragraphs (optional, for better readability)
|
||||
processedContent = processedContent.replace(/\n\n/g, '<br><br>');
|
||||
let processedContent;
|
||||
if (type === 'user') {
|
||||
// For user messages (plain text), convert newlines to <br>
|
||||
processedContent = content.replace(/\n/g, '<br>');
|
||||
processedContent = processedContent.replace(/\n\n/g, '<br><br>');
|
||||
} else {
|
||||
// For assistant messages (HTML from backend), use as-is
|
||||
processedContent = content;
|
||||
}
|
||||
div.innerHTML = processedContent;
|
||||
chatContainer.appendChild(div);
|
||||
chatContainer.scrollTop = chatContainer.scrollHeight;
|
||||
|
|
|
|||
|
|
@ -114,9 +114,9 @@ func DefaultConfig() *Config {
|
|||
ReplyTimeout: 5,
|
||||
},
|
||||
HTTP: HTTPConfig{
|
||||
Enabled: false,
|
||||
Host: "0.0.0.0",
|
||||
Port: 8080,
|
||||
Enabled: true,
|
||||
Host: "127.0.0.1",
|
||||
Port: 8070,
|
||||
AllowFrom: FlexibleStringSlice{},
|
||||
},
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue