mirror of
https://github.com/DayuanJiang/next-ai-draw-io.git
synced 2026-10-11 03:59:58 +08:00
- A redirect followed for a custom base URL also drops the key headers of providers that do not use Authorization (x-api-key, x-goog-api-key, api-key) when it goes to another origin. - A second Enter or click while a message is being prepared (attachments read, diagram exported) no longer sends it twice. - The admin Test on the deployment's own endpoints (EdgeOne, the server's keyless Ollama, an address on the server's network) counts toward the quota like a chat; the chat and the Test share one rule for it. The Test of an Azure entry set up by AZURE_RESOURCE_NAME only goes where chat goes. - EdgeOne's function is called at the site root again, as on main: EdgeOne serves edge functions there, outside Next's base path. - MCP History: the state before a write is kept unless the browser saved no change of the user's since the last server write (draw.io's sync copy of it adds no entry), and the dedupe compares the exact text again, so a change of page size or other settings only is its own version. - MCP: an edit keeps untouched labels as draw.io shows them (a literal line break in an attribute is a space); a new document of empty pages the user named is auto-saved; load_diagram reads only regular files, so a pipe cannot hold up the other write tools; the preview does not load back its own push still on its way (an undo made meanwhile is saved). - Two overlapping saves of a new chat no longer reload the canvas from the older copy. - At most three screenshot checks per user turn, passed or failed, as documented. - Desktop: the main window navigates only within the app (draw.io stays in its frame); a presets file that is not JSON and cannot be moved aside is not overwritten. - A last self-closing cell with a raw "<" in a value is not taken for cut off output. - README: Material Design shapes load their icons from fonts.gstatic.com.
99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
/**
|
|
* DOM setup for Node. The XML helpers use the global DOMParser and
|
|
* XMLSerializer, which the browser has and Node gets from here.
|
|
*
|
|
* linkedom gives us a DOM with querySelector, but it is lenient: it never
|
|
* reports syntax errors (no <parsererror>; xml-syntax.ts checks them), and
|
|
* its serializer writes raw newlines inside attribute values, which the
|
|
* browser reads back as spaces. serializeXml writes attribute values safely.
|
|
*/
|
|
import { DOMParser } from "linkedom"
|
|
|
|
const ESCAPES: Record<string, string> = {
|
|
"&": "&",
|
|
"<": "<",
|
|
">": ">",
|
|
'"': """,
|
|
"\t": "	",
|
|
"\n": "
",
|
|
"\r": "
",
|
|
}
|
|
const escapeChars = (text: string, chars: RegExp) =>
|
|
text.replace(chars, (c) => ESCAPES[c])
|
|
|
|
/**
|
|
* Serialize a linkedom node as XML. Attribute values escape tabs and line
|
|
* breaks too, so multi-line labels (value="a
b") survive a round trip.
|
|
*/
|
|
export function serializeXml(node: Node): string {
|
|
switch (node.nodeType) {
|
|
case 9: {
|
|
// Document
|
|
const root = (node as Document).documentElement
|
|
return root ? serializeXml(root) : ""
|
|
}
|
|
case 1: {
|
|
// Element
|
|
const el = node as Element
|
|
let out = `<${el.tagName}`
|
|
for (const attr of Array.from(el.attributes)) {
|
|
out += ` ${attr.name}="${escapeChars(attr.value, /[&<>"\t\n\r]/g)}"`
|
|
}
|
|
if (el.childNodes.length === 0) return `${out}/>`
|
|
out += ">"
|
|
for (const child of Array.from(el.childNodes)) {
|
|
out += serializeXml(child)
|
|
}
|
|
return `${out}</${el.tagName}>`
|
|
}
|
|
case 3:
|
|
// Text
|
|
return escapeChars(node.textContent ?? "", /[&<>]/g)
|
|
case 4:
|
|
// CDATA
|
|
return `<![CDATA[${node.textContent ?? ""}]]>`
|
|
case 8:
|
|
// Comment
|
|
return `<!--${node.textContent ?? ""}-->`
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
/**
|
|
* XML parsers read a literal tab or line break inside an attribute value as
|
|
* a space (a line break written as 
 stays one). linkedom keeps it, and
|
|
* serializeXml would then write it as a real line break, so an edit would
|
|
* change labels it never touched. Applied to the text before linkedom.
|
|
*/
|
|
function normalizeAttributeWhitespace(xml: string): string {
|
|
return xml.replace(
|
|
/<[A-Za-z][^"'<>]*(?:(?:"[^"]*"|'[^']*')[^"'<>]*)*>/g,
|
|
(tag) =>
|
|
tag.replace(/"[^"]*"|'[^']*'/g, (value) =>
|
|
value.replace(/\r\n|[\t\n\r]/g, " "),
|
|
),
|
|
)
|
|
}
|
|
|
|
class XmlDomParser extends DOMParser {
|
|
parseFromString(text: string, type: string) {
|
|
return super.parseFromString(
|
|
type.includes("xml") ? normalizeAttributeWhitespace(text) : text,
|
|
type as any,
|
|
)
|
|
}
|
|
}
|
|
|
|
class XMLSerializerPolyfill {
|
|
serializeToString(node: Node): string {
|
|
return serializeXml(node)
|
|
}
|
|
}
|
|
|
|
/** Install the DOMParser and XMLSerializer globals the XML helpers use. */
|
|
export function installDomPolyfill(): void {
|
|
;(globalThis as any).DOMParser = XmlDomParser
|
|
;(globalThis as any).XMLSerializer = XMLSerializerPolyfill
|
|
}
|