mirror of
https://github.com/open-webui/open-webui.git
synced 2025-12-11 20:05:19 +00:00
refac/fix: rich text input in ff
This commit is contained in:
parent
be7166d6fc
commit
e84c521177
3 changed files with 91 additions and 84 deletions
|
|
@ -31,6 +31,7 @@
|
|||
let content = '';
|
||||
let files = [];
|
||||
|
||||
let chatInputElement;
|
||||
let filesInputElement;
|
||||
let inputFiles;
|
||||
|
||||
|
|
@ -287,8 +288,9 @@
|
|||
|
||||
await tick();
|
||||
|
||||
const chatInputElement = document.getElementById(`chat-input-${id}`);
|
||||
chatInputElement?.focus();
|
||||
if (chatInputElement) {
|
||||
chatInputElement.focus();
|
||||
}
|
||||
};
|
||||
|
||||
$: if (content) {
|
||||
|
|
@ -297,9 +299,10 @@
|
|||
|
||||
onMount(async () => {
|
||||
window.setTimeout(() => {
|
||||
const chatInput = document.getElementById(`chat-input-${id}`);
|
||||
chatInput?.focus();
|
||||
}, 0);
|
||||
if (chatInputElement) {
|
||||
chatInputElement.focus();
|
||||
}
|
||||
}, 100);
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
await tick();
|
||||
|
|
@ -402,7 +405,10 @@
|
|||
recording = false;
|
||||
|
||||
await tick();
|
||||
document.getElementById(`chat-input-${id}`)?.focus();
|
||||
|
||||
if (chatInputElement) {
|
||||
chatInputElement.focus();
|
||||
}
|
||||
}}
|
||||
onConfirm={async (data) => {
|
||||
const { text, filename } = data;
|
||||
|
|
@ -410,7 +416,10 @@
|
|||
recording = false;
|
||||
|
||||
await tick();
|
||||
document.getElementById(`chat-input-${id}`)?.focus();
|
||||
|
||||
if (chatInputElement) {
|
||||
chatInputElement.focus();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
|
|
@ -485,17 +494,21 @@
|
|||
class="scrollbar-hidden font-primary text-left bg-transparent dark:text-gray-100 outline-hidden w-full pt-3 px-1 rounded-xl resize-none h-fit max-h-80 overflow-auto"
|
||||
>
|
||||
<RichTextInput
|
||||
bind:value={content}
|
||||
id={`chat-input-${id}`}
|
||||
bind:this={chatInputElement}
|
||||
json={true}
|
||||
messageInput={true}
|
||||
shiftEnter={!$mobile ||
|
||||
!(
|
||||
'ontouchstart' in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
navigator.msMaxTouchPoints > 0
|
||||
)}
|
||||
{placeholder}
|
||||
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
|
||||
(!$mobile ||
|
||||
!(
|
||||
'ontouchstart' in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
navigator.msMaxTouchPoints > 0
|
||||
))}
|
||||
largeTextAsFile={$settings?.largeTextAsFile ?? false}
|
||||
onChange={(e) => {
|
||||
const { md } = e;
|
||||
content = md;
|
||||
}}
|
||||
on:keydown={async (e) => {
|
||||
e = e.detail.event;
|
||||
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
|
||||
|
|
|
|||
|
|
@ -385,7 +385,6 @@
|
|||
};
|
||||
|
||||
onMount(async () => {
|
||||
console.log('codeblock', lang, code);
|
||||
if (token) {
|
||||
onUpdate(token);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -176,35 +176,34 @@
|
|||
if (!editor) return;
|
||||
text = text.replaceAll('\n\n', '\n');
|
||||
const { state, view } = editor;
|
||||
const { schema, tr } = state;
|
||||
|
||||
if (text.includes('\n')) {
|
||||
// Multiple lines: make paragraphs
|
||||
const { schema, tr } = state;
|
||||
const lines = text.split('\n');
|
||||
|
||||
// Map each line to a paragraph node (empty lines -> empty paragraph)
|
||||
const nodes = lines.map((line) =>
|
||||
schema.nodes.paragraph.create({}, line ? schema.text(line) : undefined)
|
||||
);
|
||||
|
||||
// Create a document fragment containing all parsed paragraphs
|
||||
const fragment = Fragment.fromArray(nodes);
|
||||
|
||||
// Replace current selection with these paragraphs
|
||||
tr.replaceSelectionWith(fragment, false /* don't select new */);
|
||||
|
||||
// You probably want to move the cursor after the inserted content
|
||||
// tr.setSelection(Selection.near(tr.doc.resolve(tr.selection.to)));
|
||||
|
||||
view.dispatch(tr);
|
||||
} else if (text === '') {
|
||||
// Empty: delete selection or paragraph
|
||||
editor.commands.clearContent();
|
||||
// Empty: replace with empty paragraph using tr
|
||||
const emptyParagraph = schema.nodes.paragraph.create();
|
||||
tr.replaceSelectionWith(emptyParagraph, false);
|
||||
view.dispatch(tr);
|
||||
} else {
|
||||
editor.commands.setContent(editor.state.schema.text(text));
|
||||
// Single line: create paragraph with text
|
||||
const paragraph = schema.nodes.paragraph.create({}, schema.text(text));
|
||||
tr.replaceSelectionWith(paragraph, false);
|
||||
view.dispatch(tr);
|
||||
}
|
||||
|
||||
selectNextTemplate(editor.view.state, editor.view.dispatch);
|
||||
focus();
|
||||
};
|
||||
|
||||
export const replaceVariables = (variables) => {
|
||||
|
|
@ -253,7 +252,7 @@
|
|||
export const focus = () => {
|
||||
if (editor) {
|
||||
editor.view.focus();
|
||||
// Scroll to the top of the editor
|
||||
// Scroll to the current selection
|
||||
editor.view.dispatch(editor.view.state.tr.scrollIntoView());
|
||||
}
|
||||
};
|
||||
|
|
@ -326,11 +325,7 @@
|
|||
setTimeout(() => {
|
||||
const templateFound = selectNextTemplate(editor.view.state, editor.view.dispatch);
|
||||
if (!templateFound) {
|
||||
// If no template found, set cursor at the end
|
||||
const endPos = editor.view.state.doc.content.size;
|
||||
editor.view.dispatch(
|
||||
editor.view.state.tr.setSelection(TextSelection.create(editor.view.state.doc, endPos))
|
||||
);
|
||||
editor.commands.focus('end');
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
|
@ -339,7 +334,11 @@
|
|||
onMount(async () => {
|
||||
let content = value;
|
||||
|
||||
if (!json) {
|
||||
if (json) {
|
||||
if (!content) {
|
||||
content = html ? html : null;
|
||||
}
|
||||
} else {
|
||||
if (preserveBreaks) {
|
||||
turndownService.addRule('preserveBreaks', {
|
||||
filter: 'br', // Target <br> elements
|
||||
|
|
@ -370,10 +369,6 @@
|
|||
// Usage example
|
||||
content = await tryParse(value);
|
||||
}
|
||||
} else {
|
||||
if (html && !content) {
|
||||
content = html;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('content', content);
|
||||
|
|
@ -417,40 +412,34 @@
|
|||
// force re-render so `editor.isActive` works as expected
|
||||
editor = editor;
|
||||
|
||||
html = editor.getHTML();
|
||||
const htmlValue = editor.getHTML();
|
||||
const jsonValue = editor.getJSON();
|
||||
let mdValue = turndownService
|
||||
.turndown(
|
||||
htmlValue
|
||||
.replace(/<p><\/p>/g, '<br/>')
|
||||
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||
)
|
||||
.replace(/\u00a0/g, ' ');
|
||||
|
||||
onChange({
|
||||
html: editor.getHTML(),
|
||||
json: editor.getJSON(),
|
||||
md: turndownService
|
||||
.turndown(
|
||||
editor
|
||||
.getHTML()
|
||||
.replace(/<p><\/p>/g, '<br/>')
|
||||
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||
)
|
||||
.replace(/\u00a0/g, ' ')
|
||||
html: htmlValue,
|
||||
json: jsonValue,
|
||||
md: mdValue
|
||||
});
|
||||
|
||||
if (json) {
|
||||
value = editor.getJSON();
|
||||
value = jsonValue;
|
||||
} else {
|
||||
if (!raw) {
|
||||
let newValue = turndownService
|
||||
.turndown(
|
||||
editor
|
||||
.getHTML()
|
||||
.replace(/<p><\/p>/g, '<br/>')
|
||||
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||
)
|
||||
.replace(/\u00a0/g, ' ');
|
||||
|
||||
if (raw) {
|
||||
value = htmlValue;
|
||||
} else {
|
||||
if (!preserveBreaks) {
|
||||
newValue = newValue.replace(/<br\/>/g, '');
|
||||
mdValue = mdValue.replace(/<br\/>/g, '');
|
||||
}
|
||||
|
||||
if (value !== newValue) {
|
||||
value = newValue;
|
||||
if (value !== mdValue) {
|
||||
value = mdValue;
|
||||
|
||||
// check if the node is paragraph as well
|
||||
if (editor.isActive('paragraph')) {
|
||||
|
|
@ -459,8 +448,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
value = editor.getHTML();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -609,36 +596,44 @@
|
|||
const onValueChange = () => {
|
||||
if (!editor) return;
|
||||
|
||||
const jsonValue = editor.getJSON();
|
||||
const htmlValue = editor.getHTML();
|
||||
let mdValue = turndownService
|
||||
.turndown(
|
||||
(preserveBreaks ? htmlValue.replace(/<p><\/p>/g, '<br/>') : htmlValue).replace(
|
||||
/ {2,}/g,
|
||||
(m) => m.replace(/ /g, '\u00a0')
|
||||
)
|
||||
)
|
||||
.replace(/\u00a0/g, ' ');
|
||||
|
||||
if (value === '') {
|
||||
editor.commands.clearContent(); // Clear content if value is empty
|
||||
selectTemplate();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (json) {
|
||||
if (JSON.stringify(value) !== JSON.stringify(editor.getJSON())) {
|
||||
if (JSON.stringify(value) !== JSON.stringify(jsonValue)) {
|
||||
editor.commands.setContent(value);
|
||||
selectTemplate();
|
||||
}
|
||||
} else {
|
||||
if (raw) {
|
||||
if (value !== editor.getHTML()) {
|
||||
if (value !== htmlValue) {
|
||||
editor.commands.setContent(value);
|
||||
selectTemplate();
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
value !==
|
||||
turndownService
|
||||
.turndown(
|
||||
(preserveBreaks
|
||||
? editor.getHTML().replace(/<p><\/p>/g, '<br/>')
|
||||
: editor.getHTML()
|
||||
).replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
||||
)
|
||||
.replace(/\u00a0/g, ' ')
|
||||
) {
|
||||
preserveBreaks
|
||||
? editor.commands.setContent(value)
|
||||
: editor.commands.setContent(
|
||||
marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
|
||||
if (value !== mdValue) {
|
||||
editor.commands.setContent(
|
||||
preserveBreaks
|
||||
? value
|
||||
: marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
|
||||
breaks: false
|
||||
})
|
||||
); // Update editor content
|
||||
);
|
||||
|
||||
selectTemplate();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue