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 content = '';
|
||||||
let files = [];
|
let files = [];
|
||||||
|
|
||||||
|
let chatInputElement;
|
||||||
let filesInputElement;
|
let filesInputElement;
|
||||||
let inputFiles;
|
let inputFiles;
|
||||||
|
|
||||||
|
|
@ -287,8 +288,9 @@
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
const chatInputElement = document.getElementById(`chat-input-${id}`);
|
if (chatInputElement) {
|
||||||
chatInputElement?.focus();
|
chatInputElement.focus();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$: if (content) {
|
$: if (content) {
|
||||||
|
|
@ -297,9 +299,10 @@
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
const chatInput = document.getElementById(`chat-input-${id}`);
|
if (chatInputElement) {
|
||||||
chatInput?.focus();
|
chatInputElement.focus();
|
||||||
}, 0);
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
window.addEventListener('keydown', handleKeyDown);
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
await tick();
|
await tick();
|
||||||
|
|
@ -402,7 +405,10 @@
|
||||||
recording = false;
|
recording = false;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
document.getElementById(`chat-input-${id}`)?.focus();
|
|
||||||
|
if (chatInputElement) {
|
||||||
|
chatInputElement.focus();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onConfirm={async (data) => {
|
onConfirm={async (data) => {
|
||||||
const { text, filename } = data;
|
const { text, filename } = data;
|
||||||
|
|
@ -410,7 +416,10 @@
|
||||||
recording = false;
|
recording = false;
|
||||||
|
|
||||||
await tick();
|
await tick();
|
||||||
document.getElementById(`chat-input-${id}`)?.focus();
|
|
||||||
|
if (chatInputElement) {
|
||||||
|
chatInputElement.focus();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{: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"
|
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
|
<RichTextInput
|
||||||
bind:value={content}
|
bind:this={chatInputElement}
|
||||||
id={`chat-input-${id}`}
|
json={true}
|
||||||
messageInput={true}
|
messageInput={true}
|
||||||
shiftEnter={!$mobile ||
|
shiftEnter={!($settings?.ctrlEnterToSend ?? false) &&
|
||||||
!(
|
(!$mobile ||
|
||||||
'ontouchstart' in window ||
|
!(
|
||||||
navigator.maxTouchPoints > 0 ||
|
'ontouchstart' in window ||
|
||||||
navigator.msMaxTouchPoints > 0
|
navigator.maxTouchPoints > 0 ||
|
||||||
)}
|
navigator.msMaxTouchPoints > 0
|
||||||
{placeholder}
|
))}
|
||||||
largeTextAsFile={$settings?.largeTextAsFile ?? false}
|
largeTextAsFile={$settings?.largeTextAsFile ?? false}
|
||||||
|
onChange={(e) => {
|
||||||
|
const { md } = e;
|
||||||
|
content = md;
|
||||||
|
}}
|
||||||
on:keydown={async (e) => {
|
on:keydown={async (e) => {
|
||||||
e = e.detail.event;
|
e = e.detail.event;
|
||||||
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
|
const isCtrlPressed = e.ctrlKey || e.metaKey; // metaKey is for Cmd key on Mac
|
||||||
|
|
|
||||||
|
|
@ -385,7 +385,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
console.log('codeblock', lang, code);
|
|
||||||
if (token) {
|
if (token) {
|
||||||
onUpdate(token);
|
onUpdate(token);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,35 +176,34 @@
|
||||||
if (!editor) return;
|
if (!editor) return;
|
||||||
text = text.replaceAll('\n\n', '\n');
|
text = text.replaceAll('\n\n', '\n');
|
||||||
const { state, view } = editor;
|
const { state, view } = editor;
|
||||||
|
const { schema, tr } = state;
|
||||||
|
|
||||||
if (text.includes('\n')) {
|
if (text.includes('\n')) {
|
||||||
// Multiple lines: make paragraphs
|
// Multiple lines: make paragraphs
|
||||||
const { schema, tr } = state;
|
|
||||||
const lines = text.split('\n');
|
const lines = text.split('\n');
|
||||||
|
|
||||||
// Map each line to a paragraph node (empty lines -> empty paragraph)
|
// Map each line to a paragraph node (empty lines -> empty paragraph)
|
||||||
const nodes = lines.map((line) =>
|
const nodes = lines.map((line) =>
|
||||||
schema.nodes.paragraph.create({}, line ? schema.text(line) : undefined)
|
schema.nodes.paragraph.create({}, line ? schema.text(line) : undefined)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Create a document fragment containing all parsed paragraphs
|
// Create a document fragment containing all parsed paragraphs
|
||||||
const fragment = Fragment.fromArray(nodes);
|
const fragment = Fragment.fromArray(nodes);
|
||||||
|
|
||||||
// Replace current selection with these paragraphs
|
// Replace current selection with these paragraphs
|
||||||
tr.replaceSelectionWith(fragment, false /* don't select new */);
|
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);
|
view.dispatch(tr);
|
||||||
} else if (text === '') {
|
} else if (text === '') {
|
||||||
// Empty: delete selection or paragraph
|
// Empty: replace with empty paragraph using tr
|
||||||
editor.commands.clearContent();
|
const emptyParagraph = schema.nodes.paragraph.create();
|
||||||
|
tr.replaceSelectionWith(emptyParagraph, false);
|
||||||
|
view.dispatch(tr);
|
||||||
} else {
|
} 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);
|
selectNextTemplate(editor.view.state, editor.view.dispatch);
|
||||||
|
focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const replaceVariables = (variables) => {
|
export const replaceVariables = (variables) => {
|
||||||
|
|
@ -253,7 +252,7 @@
|
||||||
export const focus = () => {
|
export const focus = () => {
|
||||||
if (editor) {
|
if (editor) {
|
||||||
editor.view.focus();
|
editor.view.focus();
|
||||||
// Scroll to the top of the editor
|
// Scroll to the current selection
|
||||||
editor.view.dispatch(editor.view.state.tr.scrollIntoView());
|
editor.view.dispatch(editor.view.state.tr.scrollIntoView());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -326,11 +325,7 @@
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const templateFound = selectNextTemplate(editor.view.state, editor.view.dispatch);
|
const templateFound = selectNextTemplate(editor.view.state, editor.view.dispatch);
|
||||||
if (!templateFound) {
|
if (!templateFound) {
|
||||||
// If no template found, set cursor at the end
|
editor.commands.focus('end');
|
||||||
const endPos = editor.view.state.doc.content.size;
|
|
||||||
editor.view.dispatch(
|
|
||||||
editor.view.state.tr.setSelection(TextSelection.create(editor.view.state.doc, endPos))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
@ -339,7 +334,11 @@
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let content = value;
|
let content = value;
|
||||||
|
|
||||||
if (!json) {
|
if (json) {
|
||||||
|
if (!content) {
|
||||||
|
content = html ? html : null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (preserveBreaks) {
|
if (preserveBreaks) {
|
||||||
turndownService.addRule('preserveBreaks', {
|
turndownService.addRule('preserveBreaks', {
|
||||||
filter: 'br', // Target <br> elements
|
filter: 'br', // Target <br> elements
|
||||||
|
|
@ -370,10 +369,6 @@
|
||||||
// Usage example
|
// Usage example
|
||||||
content = await tryParse(value);
|
content = await tryParse(value);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if (html && !content) {
|
|
||||||
content = html;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('content', content);
|
console.log('content', content);
|
||||||
|
|
@ -417,40 +412,34 @@
|
||||||
// force re-render so `editor.isActive` works as expected
|
// force re-render so `editor.isActive` works as expected
|
||||||
editor = editor;
|
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({
|
onChange({
|
||||||
html: editor.getHTML(),
|
html: htmlValue,
|
||||||
json: editor.getJSON(),
|
json: jsonValue,
|
||||||
md: turndownService
|
md: mdValue
|
||||||
.turndown(
|
|
||||||
editor
|
|
||||||
.getHTML()
|
|
||||||
.replace(/<p><\/p>/g, '<br/>')
|
|
||||||
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
|
||||||
)
|
|
||||||
.replace(/\u00a0/g, ' ')
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (json) {
|
if (json) {
|
||||||
value = editor.getJSON();
|
value = jsonValue;
|
||||||
} else {
|
} else {
|
||||||
if (!raw) {
|
if (raw) {
|
||||||
let newValue = turndownService
|
value = htmlValue;
|
||||||
.turndown(
|
} else {
|
||||||
editor
|
|
||||||
.getHTML()
|
|
||||||
.replace(/<p><\/p>/g, '<br/>')
|
|
||||||
.replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0'))
|
|
||||||
)
|
|
||||||
.replace(/\u00a0/g, ' ');
|
|
||||||
|
|
||||||
if (!preserveBreaks) {
|
if (!preserveBreaks) {
|
||||||
newValue = newValue.replace(/<br\/>/g, '');
|
mdValue = mdValue.replace(/<br\/>/g, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value !== newValue) {
|
if (value !== mdValue) {
|
||||||
value = newValue;
|
value = mdValue;
|
||||||
|
|
||||||
// check if the node is paragraph as well
|
// check if the node is paragraph as well
|
||||||
if (editor.isActive('paragraph')) {
|
if (editor.isActive('paragraph')) {
|
||||||
|
|
@ -459,8 +448,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
value = editor.getHTML();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -609,36 +596,44 @@
|
||||||
const onValueChange = () => {
|
const onValueChange = () => {
|
||||||
if (!editor) return;
|
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) {
|
||||||
if (JSON.stringify(value) !== JSON.stringify(editor.getJSON())) {
|
if (JSON.stringify(value) !== JSON.stringify(jsonValue)) {
|
||||||
editor.commands.setContent(value);
|
editor.commands.setContent(value);
|
||||||
selectTemplate();
|
selectTemplate();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (raw) {
|
if (raw) {
|
||||||
if (value !== editor.getHTML()) {
|
if (value !== htmlValue) {
|
||||||
editor.commands.setContent(value);
|
editor.commands.setContent(value);
|
||||||
selectTemplate();
|
selectTemplate();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (
|
if (value !== mdValue) {
|
||||||
value !==
|
editor.commands.setContent(
|
||||||
turndownService
|
preserveBreaks
|
||||||
.turndown(
|
? value
|
||||||
(preserveBreaks
|
: marked.parse(value.replaceAll(`\n<br/>`, `<br/>`), {
|
||||||
? 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/>`), {
|
|
||||||
breaks: false
|
breaks: false
|
||||||
})
|
})
|
||||||
); // Update editor content
|
);
|
||||||
|
|
||||||
selectTemplate();
|
selectTemplate();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue