feat: message format, pretty markdown
This commit is contained in:
1 parent
3d8f77d154
commit
5c7a106413
7 files changed
+95
-15
No files matched your search
@@ -13,6 +13,7 @@ def message_json(msg):
|
||||
"sender": msg.sender,
|
||||
"kind": msg.kind,
|
||||
"subkind": msg.subkind,
|
||||
"format": msg.format,
|
||||
"content": msg.content,
|
||||
"threadId": msg.thread_id,
|
||||
}
|
||||
|
||||
@@ -276,7 +276,7 @@ def register(server, app, auth_manager):
|
||||
elif action == "update-chat-message":
|
||||
message = await app.update_chat_message(
|
||||
message_id=body['messageId'],
|
||||
content=body['content'],
|
||||
content=body.get('content'),
|
||||
kind=body.get('kind'),
|
||||
subkind=body.get('subkind'),
|
||||
format=body.get('format'),
|
||||
|
||||
@@ -95,3 +95,13 @@ input:disabled, textarea:disabled, select:disabled {
|
||||
.with-sidebar-expanded .when-sidebar-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Content */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 1em 0 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
p, ul, il {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
@@ -545,6 +545,33 @@ export class ChatSession {
|
||||
this.creatingThread.value = Boolean(fork);
|
||||
}
|
||||
|
||||
// Sets the render format of a message ("markdown", or "" for plain text).
|
||||
// Omit `content` so the backend leaves the existing content untouched.
|
||||
setMessageFormat (message, format) {
|
||||
backend.sendAction("update-chat-message", {
|
||||
messageId: message.id, format,
|
||||
}).then(response => {
|
||||
if (response && response.error) {
|
||||
ui.alert("Error updating message format: " + response.error);
|
||||
}
|
||||
});
|
||||
|
||||
// No update event is broadcast (matching the content-edit path above), so
|
||||
// patch the source message and rebuild the entries locally. Other sessions
|
||||
// and stale views won't reflect this until reload — which is correct, since
|
||||
// the format is persisted. If we ever broadcast edits, flip this to rely on
|
||||
// the incoming update instead.
|
||||
let thread = this.getThread(message.threadId);
|
||||
let existing = thread.messages.find(msg => msg.id == message.id);
|
||||
if (existing) {
|
||||
existing.format = format;
|
||||
} else {
|
||||
console.error("Can't set format locally?", {messageId: message.id, thread});
|
||||
}
|
||||
|
||||
this.computeChatEntries();
|
||||
}
|
||||
|
||||
// Adds a bulk of messages to the message list, in their correct
|
||||
// chronological position and replacing duplicates
|
||||
addMessageBulk (messages) {
|
||||
|
||||
@@ -4,13 +4,17 @@ import { marked } from "marked";
|
||||
import * as backend from '../backend';
|
||||
marked.use({
|
||||
renderer: {
|
||||
code (code, infostring, escaped) {
|
||||
let langElem = infostring ?
|
||||
`<div class="language">${infostring}</div>` : "";
|
||||
code ({lang, text}) {
|
||||
let langElem = lang ?
|
||||
`<div class="code-lang">${lang}</div>` : "";
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
const escaped = div.innerHTML;
|
||||
|
||||
return `<div class="code">
|
||||
${langElem}
|
||||
<div class="content">${code}</div>
|
||||
<div class="code-content">${escaped}</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
@@ -111,7 +115,7 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
||||
<template v-if="message.type == 'message' || message.type == null">
|
||||
<div v-if="message.sender != 'user'" class="sender">{{message.sender}}</div>
|
||||
|
||||
<div v-if="format == 'markdown'" v-html="marked.parse(message.content)" class="content"></div>
|
||||
<div v-if="format == 'markdown'" v-html="marked.parse(message.content)" class="md-content" style="white-space: collapse;"></div>
|
||||
<div v-else class="content">{{content}}</div>
|
||||
</template>
|
||||
|
||||
@@ -189,14 +193,6 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
||||
margin-right: 2em;
|
||||
}
|
||||
|
||||
.message .content {
|
||||
white-space: preserve;
|
||||
}
|
||||
|
||||
.message ul, .message ol {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.message .sender,
|
||||
.note .sender {
|
||||
font-weight: bold;
|
||||
@@ -288,4 +284,38 @@ watch(() => [props.message.format, props.message.content], ([newFormat, newConte
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.md-content {
|
||||
white-space: collapse;
|
||||
width: fit-content;
|
||||
max-width: 50em;
|
||||
line-height: normal;
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* Code blocks are generated at runtime, these styles cannot be scoped */
|
||||
|
||||
.code {
|
||||
background-color: var(--color-surface-input);
|
||||
border-radius: var(--border-radius);
|
||||
margin: .5em 0;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
.code-lang {
|
||||
padding-bottom: .5em;
|
||||
margin-top: -.5em;
|
||||
font-weight: bold;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.code-content {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
overflow-x: auto;
|
||||
line-height: 1.6rem;
|
||||
}
|
||||
</style>
|
||||
@@ -23,8 +23,8 @@
|
||||
<style scoped>
|
||||
:slotted(h1) {
|
||||
font-size: 160%;
|
||||
font-weight: bold;
|
||||
padding: 0 0.4em;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.main-header {
|
||||
|
||||
@@ -130,6 +130,12 @@
|
||||
{value: 'pin', title: "Pin message"},
|
||||
{value: 'edit', title: "Edit in-place"},
|
||||
{value: 'edit-fork', title: "Edit in new thread"},
|
||||
// ContextMenu.vue uses null for "dismissed", so the "None" entry can't
|
||||
// carry a null value; "format:" (empty suffix) is the sentinel for "".
|
||||
{title: "Set format", entries: [
|
||||
{value: 'format:markdown', title: "Markdown"},
|
||||
{value: 'format:', title: "None"},
|
||||
]},
|
||||
];
|
||||
|
||||
// Long logs can be expanded/collapsed
|
||||
@@ -164,6 +170,12 @@
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (selected?.startsWith('format:')) {
|
||||
session.setMessageFormat(message, selected.slice('format:'.length));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user