mirror of
https://github.com/open-webui/open-webui.git
synced 2026-10-19 01:51:15 -05:00
refac
This commit is contained in:
1 parent
4ebdd4a75c
commit
15ac5163ea
3 files changed
+115
-19
No files matched your search
@@ -423,6 +423,8 @@
|
||||
{#key selectedFile}
|
||||
<MarkdownDocument
|
||||
content={fileContent}
|
||||
onSave={readOnly ? null : onSave}
|
||||
bind:saving
|
||||
id={`file-${selectedFile}`}
|
||||
path={selectedFile ?? ''}
|
||||
{baseUrl}
|
||||
|
||||
@@ -17,13 +17,17 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { getAllContexts, mount, unmount, setContext, onDestroy } from 'svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { downloadFileBlob } from '$lib/apis/terminal';
|
||||
import MarkdownTokens from '../Messages/Markdown/MarkdownTokens.svelte';
|
||||
import type { Token } from 'marked';
|
||||
import type { Token, Tokens } from 'marked';
|
||||
import { toast } from 'svelte-sonner';
|
||||
|
||||
export let content: string;
|
||||
export let onSave: ((content: string) => Promise<void>) | null = null;
|
||||
export let saving = false;
|
||||
export let id: string;
|
||||
export let path = '';
|
||||
export let baseUrl = '';
|
||||
@@ -95,8 +99,69 @@
|
||||
release();
|
||||
}
|
||||
|
||||
function markdownTasks(tokens: Token[]) {
|
||||
const tasks: Tokens.ListItem[] = [];
|
||||
documentMarkdown.walkTokens(tokens, (token) => {
|
||||
if (token.type === 'list_item' && token.task) tasks.push(token as Tokens.ListItem);
|
||||
});
|
||||
return tasks;
|
||||
}
|
||||
|
||||
function toggleMarkdownTask(content: string, index: number, checked: boolean) {
|
||||
const tasks = markdownTasks(documentMarkdown.lexer(content));
|
||||
if (!tasks[index]) throw new Error('Task no longer exists. Reload the file and try again.');
|
||||
if (tasks[index].checked === checked) return content;
|
||||
|
||||
// Confirm the marker with the renderer's parser, so duplicates and code examples stay untouched.
|
||||
for (const match of content.matchAll(/\[[ xX]\]/g)) {
|
||||
const offset = match.index! + 1;
|
||||
if ((content[offset].toLowerCase() === 'x') === checked) continue;
|
||||
const updated = content.slice(0, offset) + (checked ? 'x' : ' ') + content.slice(offset + 1);
|
||||
const next = markdownTasks(documentMarkdown.lexer(updated));
|
||||
if (
|
||||
next.length === tasks.length &&
|
||||
next.every((task, i) => task.checked === (i === index ? checked : tasks[i].checked))
|
||||
) {
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
throw new Error('Could not locate the task in the file. Reload the file and try again.');
|
||||
}
|
||||
|
||||
const taskEnabled = writable(false);
|
||||
$: taskEnabled.set(!!onSave);
|
||||
setContext('markdown', {
|
||||
document: true,
|
||||
taskEnabled,
|
||||
onTaskClick: async ({
|
||||
item,
|
||||
checked,
|
||||
input
|
||||
}: {
|
||||
item: Tokens.ListItem;
|
||||
checked: boolean;
|
||||
input: HTMLInputElement;
|
||||
}) => {
|
||||
input.checked = !!item.checked;
|
||||
if (!onSave || saving) return;
|
||||
const previous = content;
|
||||
const wasChecked = !!item.checked;
|
||||
saving = true;
|
||||
try {
|
||||
const index = markdownTasks(tokens).indexOf(item);
|
||||
const updated = toggleMarkdownTask(content, index, checked);
|
||||
// Keep the rendered document in place while saving this single marker.
|
||||
parsedContent = content = updated;
|
||||
item.checked = input.checked = checked;
|
||||
await onSave(updated);
|
||||
} catch (error) {
|
||||
parsedContent = content = previous;
|
||||
item.checked = input.checked = wasChecked;
|
||||
toast.error(String(error));
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
},
|
||||
allowRemoteContent: true,
|
||||
lexer: (source: string) => documentMarkdown.lexer(source),
|
||||
parseInline: (tokens: Token[]) =>
|
||||
@@ -142,7 +207,12 @@
|
||||
update(props);
|
||||
return { update, destroy };
|
||||
}
|
||||
$: tokens = documentMarkdown.lexer(content);
|
||||
let parsedContent: string | undefined;
|
||||
let tokens: Token[] = [];
|
||||
$: if (content !== parsedContent) {
|
||||
tokens = documentMarkdown.lexer(content);
|
||||
parsedContent = content;
|
||||
}
|
||||
const resolveHtmlImages = (node: HTMLElement) => {
|
||||
let alive = true;
|
||||
const update = () => {
|
||||
@@ -261,7 +331,7 @@
|
||||
.document-markdown :global(.codespan) {
|
||||
font-size: 0.95em;
|
||||
}
|
||||
.document-markdown :global(input[type='checkbox']) {
|
||||
.document-markdown :global(input[type='checkbox']:disabled) {
|
||||
cursor: default;
|
||||
}
|
||||
.document-markdown :global(.document-code-block) {
|
||||
|
||||
@@ -1,21 +1,30 @@
|
||||
<script lang="ts">
|
||||
import { decode } from 'html-entities';
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import type { Readable } from 'svelte/store';
|
||||
import type { Action } from 'svelte/action';
|
||||
const i18n = getContext('i18n');
|
||||
const markdown = getContext<
|
||||
| {
|
||||
document?: boolean;
|
||||
taskEnabled?: Readable<boolean>;
|
||||
onTaskClick?: (event: {
|
||||
item: Tokens.ListItem;
|
||||
checked: boolean;
|
||||
input: HTMLInputElement;
|
||||
}) => void;
|
||||
lexer?: (source: string) => Token[];
|
||||
renderHtml?: Action<HTMLElement, { id: string; tokens: Token[]; top: boolean }>;
|
||||
}
|
||||
| undefined
|
||||
>('markdown');
|
||||
|
||||
const taskEnabled = markdown?.taskEnabled;
|
||||
|
||||
import fileSaver from 'file-saver';
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
import { type Token } from 'marked';
|
||||
import { type Token, type Tokens } from 'marked';
|
||||
import { copyToClipboard, unescapeHtml } from '$lib/utils';
|
||||
import { resolveChatMessageToolCall } from '$lib/apis/chats';
|
||||
|
||||
@@ -340,18 +349,19 @@
|
||||
<li dir="auto" class="text-start">
|
||||
{#if item?.task}
|
||||
<input
|
||||
class=" translate-y-[1px] -translate-x-1 [&:dir(rtl)]:translate-x-1 flex-shrink-0"
|
||||
class="task-checkbox translate-y-[1px] -translate-x-1 [&:dir(rtl)]:translate-x-1 flex-shrink-0"
|
||||
type="checkbox"
|
||||
disabled={markdown?.document ?? false}
|
||||
disabled={markdown?.document && !$taskEnabled}
|
||||
checked={item.checked}
|
||||
on:change={(e) => {
|
||||
onTaskClick({
|
||||
(markdown?.onTaskClick ?? onTaskClick)({
|
||||
id: id,
|
||||
token: token,
|
||||
tokenIdx: tokenIdx,
|
||||
item: item,
|
||||
itemIdx: itemIdx,
|
||||
checked: e.target.checked
|
||||
input: e.currentTarget,
|
||||
checked: e.currentTarget.checked
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -379,31 +389,27 @@
|
||||
{:else}
|
||||
<ul dir="auto" class="">
|
||||
{#each token.items as item, itemIdx}
|
||||
<li
|
||||
dir="auto"
|
||||
class="text-start {item?.task
|
||||
? 'flex -translate-x-6.5 [&:dir(rtl)]:translate-x-6.5 gap-3 '
|
||||
: ''}"
|
||||
>
|
||||
<li dir="auto" class="text-start {item?.task ? 'relative list-none' : ''}">
|
||||
{#if item?.task}
|
||||
<input
|
||||
class="flex-shrink-0"
|
||||
class="task-checkbox"
|
||||
type="checkbox"
|
||||
disabled={markdown?.document ?? false}
|
||||
disabled={markdown?.document && !$taskEnabled}
|
||||
checked={item.checked}
|
||||
on:change={(e) => {
|
||||
onTaskClick({
|
||||
(markdown?.onTaskClick ?? onTaskClick)({
|
||||
id: id,
|
||||
token: token,
|
||||
tokenIdx: tokenIdx,
|
||||
item: item,
|
||||
itemIdx: itemIdx,
|
||||
checked: e.target.checked
|
||||
input: e.currentTarget,
|
||||
checked: e.currentTarget.checked
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div class="task-content">
|
||||
<svelte:self
|
||||
id={`${id}-${tokenIdx}-${itemIdx}`}
|
||||
{chatId}
|
||||
@@ -673,4 +679,22 @@
|
||||
li {
|
||||
margin-inline-start: 1.5em;
|
||||
}
|
||||
|
||||
ul > li > input.task-checkbox {
|
||||
position: absolute;
|
||||
inset-inline-start: -1.5em;
|
||||
top: calc((1lh - 0.875rem) / 2);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
input.task-checkbox::after {
|
||||
inset: 0;
|
||||
width: auto;
|
||||
height: auto;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
.task-content > :global(p:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user