Merge remote-tracking branch 'gitea/develop' into fix/review-batch-2

# Conflicts:
#	AGENTS.md
#	CHANGELOG.md
#	README.md
#	apps/server/src/collaboration/collaboration.handler.ts
#	apps/server/src/common/helpers/prosemirror/html-embed.spec.ts
#	apps/server/src/common/helpers/prosemirror/html-embed.util.ts
#	apps/server/src/core/ai-chat/public-share-chat.service.ts
#	apps/server/src/core/ai-chat/public-share-chat.spec.ts
#	apps/server/src/core/ai-chat/public-share-workspace-limiter.ts
#	apps/server/src/core/page/services/page.service.ts
#	apps/server/src/core/page/transclusion/transclusion.service.ts
#	apps/server/src/integrations/import/services/file-import-task.service.ts
#	apps/server/src/integrations/import/services/import.service.ts
This commit is contained in:
claude code agent 227
2026-06-21 05:32:44 +03:00
65 changed files with 1448 additions and 2927 deletions

View File

@@ -7,8 +7,10 @@ export interface HtmlEmbedOptions {
}
export interface HtmlEmbedAttributes {
// Raw HTML/CSS/JS string that is injected verbatim into the wiki origin.
// Raw HTML/CSS/JS string rendered inside a sandboxed iframe by the NodeView.
source?: string;
// Fixed iframe height in pixels. null/absent => auto-resize via postMessage.
height?: number | null;
}
declare module "@tiptap/core" {
@@ -98,6 +100,21 @@ export const HtmlEmbed = Node.create<HtmlEmbedOptions>({
"data-source": encodeHtmlEmbedSource(attributes.source || ""),
}),
},
// Fixed iframe height in px. null/absent => auto-resize on the client.
height: {
default: null,
parseHTML: (el) => {
const v = el.getAttribute("data-height");
if (!v) return null;
const n = parseInt(v, 10);
// A non-numeric data-height (e.g. crafted/corrupted import) must not
// become NaN: NaN is typeof "number" and would disable auto-resize and
// yield an unclamped iframe height downstream. Treat it as auto (null).
return Number.isFinite(n) ? n : null;
},
renderHTML: (attrs: HtmlEmbedAttributes) =>
attrs.height ? { "data-height": String(attrs.height) } : {},
},
};
},

View File

@@ -797,6 +797,60 @@ const Embed = Node.create({
},
});
/**
* Docmost raw HTML embed. Block atom; the client renders `source` inside a
* sandboxed iframe. The MCP server never renders it — it only needs the
* schema to accept and carry the node so a fromYdoc -> transform -> toYdoc
* round-trip does not throw "Unknown node type: htmlEmbed". Mirrors the
* @docmost/editor-ext node name, attribute keys and flags; keep in sync when
* the editor-ext htmlEmbed schema changes.
*
* NOTE: unlike the canonical editor-ext node, `data-source` here is mapped as
* plain text rather than base64-encoded. That is intentional: the MCP write
* path carries the node through Yjs (fromYdoc -> toYdoc) on its JSON `source`
* attribute and never invokes parseHTML/renderHTML, and htmlEmbed is not
* produced from the markdown/HTML (generateJSON) path. If a future HTML path
* for htmlEmbed is added here, this mapping must adopt editor-ext's base64
* encode/decode to avoid double-encoding `source`.
*/
const HtmlEmbed = Node.create({
name: "htmlEmbed",
group: "block",
inline: false,
isolating: true,
atom: true,
defining: true,
draggable: true,
addAttributes() {
return {
source: {
default: "",
parseHTML: (el: HTMLElement) => el.getAttribute("data-source") ?? "",
renderHTML: (attrs: Record<string, any>) => ({
"data-source": attrs.source ?? "",
}),
},
height: {
default: null,
parseHTML: (el: HTMLElement) => {
const v = el.getAttribute("data-height");
if (!v) return null;
const n = parseInt(v, 10);
return Number.isFinite(n) ? n : null;
},
renderHTML: (attrs: Record<string, any>) =>
attrs.height != null ? { "data-height": String(attrs.height) } : {},
},
};
},
parseHTML() {
return [{ tag: 'div[data-type="htmlEmbed"]' }];
},
renderHTML({ HTMLAttributes }) {
return ["div", { "data-type": "htmlEmbed", ...HTMLAttributes }, 0];
},
});
/** Shared attribute set for drawio/excalidraw diagram nodes. */
const diagramAttributes = () => ({
src: {
@@ -1158,6 +1212,7 @@ export const docmostExtensions = [
Video,
Youtube,
Embed,
HtmlEmbed,
Drawio,
Excalidraw,
Columns,