chore: import upstream snapshot with attribution
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Has been cancelled
Build and Push Docker Images / finalize_release (push) Has been cancelled
Obsidian Plugin Lint / lint (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / compute_version (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / verify_digests (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Has been cancelled
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Has been cancelled
Build and Push Docker Images / finalize_release (push) Has been cancelled
Obsidian Plugin Lint / lint (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / compute_version (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / verify_digests (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
"use client";
|
||||
|
||||
import type { AutoformatRule } from "@platejs/autoformat";
|
||||
|
||||
import {
|
||||
AutoformatPlugin,
|
||||
autoformatArrow,
|
||||
autoformatLegal,
|
||||
autoformatLegalHtml,
|
||||
autoformatMath,
|
||||
autoformatPunctuation,
|
||||
autoformatSmartQuotes,
|
||||
} from "@platejs/autoformat";
|
||||
import { insertEmptyCodeBlock } from "@platejs/code-block";
|
||||
import { toggleList } from "@platejs/list";
|
||||
import { openNextToggles } from "@platejs/toggle/react";
|
||||
import { KEYS } from "platejs";
|
||||
|
||||
const autoformatMarks: AutoformatRule[] = [
|
||||
{
|
||||
match: "***",
|
||||
mode: "mark",
|
||||
type: [KEYS.bold, KEYS.italic],
|
||||
},
|
||||
{
|
||||
match: "__*",
|
||||
mode: "mark",
|
||||
type: [KEYS.underline, KEYS.italic],
|
||||
},
|
||||
{
|
||||
match: "__**",
|
||||
mode: "mark",
|
||||
type: [KEYS.underline, KEYS.bold],
|
||||
},
|
||||
{
|
||||
match: "___***",
|
||||
mode: "mark",
|
||||
type: [KEYS.underline, KEYS.bold, KEYS.italic],
|
||||
},
|
||||
{
|
||||
match: "**",
|
||||
mode: "mark",
|
||||
type: KEYS.bold,
|
||||
},
|
||||
{
|
||||
match: "__",
|
||||
mode: "mark",
|
||||
type: KEYS.underline,
|
||||
},
|
||||
{
|
||||
match: "*",
|
||||
mode: "mark",
|
||||
type: KEYS.italic,
|
||||
},
|
||||
{
|
||||
match: "_",
|
||||
mode: "mark",
|
||||
type: KEYS.italic,
|
||||
},
|
||||
{
|
||||
match: "~~",
|
||||
mode: "mark",
|
||||
type: KEYS.strikethrough,
|
||||
},
|
||||
{
|
||||
match: "^",
|
||||
mode: "mark",
|
||||
type: KEYS.sup,
|
||||
},
|
||||
{
|
||||
match: "~",
|
||||
mode: "mark",
|
||||
type: KEYS.sub,
|
||||
},
|
||||
{
|
||||
match: "==",
|
||||
mode: "mark",
|
||||
type: KEYS.highlight,
|
||||
},
|
||||
{
|
||||
match: "≡",
|
||||
mode: "mark",
|
||||
type: KEYS.highlight,
|
||||
},
|
||||
{
|
||||
match: "`",
|
||||
mode: "mark",
|
||||
type: KEYS.code,
|
||||
},
|
||||
];
|
||||
|
||||
const autoformatBlocks: AutoformatRule[] = [
|
||||
{
|
||||
match: "# ",
|
||||
mode: "block",
|
||||
type: KEYS.h1,
|
||||
},
|
||||
{
|
||||
match: "## ",
|
||||
mode: "block",
|
||||
type: KEYS.h2,
|
||||
},
|
||||
{
|
||||
match: "### ",
|
||||
mode: "block",
|
||||
type: KEYS.h3,
|
||||
},
|
||||
{
|
||||
match: "#### ",
|
||||
mode: "block",
|
||||
type: KEYS.h4,
|
||||
},
|
||||
{
|
||||
match: "##### ",
|
||||
mode: "block",
|
||||
type: KEYS.h5,
|
||||
},
|
||||
{
|
||||
match: "###### ",
|
||||
mode: "block",
|
||||
type: KEYS.h6,
|
||||
},
|
||||
{
|
||||
match: "> ",
|
||||
mode: "block",
|
||||
type: KEYS.blockquote,
|
||||
},
|
||||
{
|
||||
match: "```",
|
||||
mode: "block",
|
||||
type: KEYS.codeBlock,
|
||||
format: (editor) => {
|
||||
insertEmptyCodeBlock(editor, {
|
||||
defaultType: KEYS.p,
|
||||
insertNodesOptions: { select: true },
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
match: "+ ",
|
||||
mode: "block",
|
||||
preFormat: openNextToggles,
|
||||
type: KEYS.toggle,
|
||||
},
|
||||
{
|
||||
match: ["---", "—-", "___ "],
|
||||
mode: "block",
|
||||
type: KEYS.hr,
|
||||
format: (editor) => {
|
||||
editor.tf.setNodes({ type: KEYS.hr });
|
||||
editor.tf.insertNodes({
|
||||
children: [{ text: "" }],
|
||||
type: KEYS.p,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const autoformatLists: AutoformatRule[] = [
|
||||
{
|
||||
match: ["* ", "- "],
|
||||
mode: "block",
|
||||
type: "list",
|
||||
format: (editor) => {
|
||||
toggleList(editor, {
|
||||
listStyleType: KEYS.ul,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
match: [String.raw`^\d+\.$ `, String.raw`^\d+\)$ `],
|
||||
matchByRegex: true,
|
||||
mode: "block",
|
||||
type: "list",
|
||||
format: (editor, { matchString }) => {
|
||||
toggleList(editor, {
|
||||
listRestartPolite: Number(matchString) || 1,
|
||||
listStyleType: KEYS.ol,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
match: ["[] "],
|
||||
mode: "block",
|
||||
type: "list",
|
||||
format: (editor) => {
|
||||
toggleList(editor, {
|
||||
listStyleType: KEYS.listTodo,
|
||||
});
|
||||
editor.tf.setNodes({
|
||||
checked: false,
|
||||
listStyleType: KEYS.listTodo,
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
match: ["[x] "],
|
||||
mode: "block",
|
||||
type: "list",
|
||||
format: (editor) => {
|
||||
toggleList(editor, {
|
||||
listStyleType: KEYS.listTodo,
|
||||
});
|
||||
editor.tf.setNodes({
|
||||
checked: true,
|
||||
listStyleType: KEYS.listTodo,
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const AutoformatKit = [
|
||||
AutoformatPlugin.configure({
|
||||
options: {
|
||||
enableUndoOnDelete: true,
|
||||
rules: [
|
||||
...autoformatBlocks,
|
||||
...autoformatMarks,
|
||||
...autoformatSmartQuotes,
|
||||
...autoformatPunctuation,
|
||||
...autoformatLegal,
|
||||
...autoformatLegalHtml,
|
||||
...autoformatArrow,
|
||||
...autoformatMath,
|
||||
...autoformatLists,
|
||||
].map(
|
||||
(rule): AutoformatRule => ({
|
||||
...rule,
|
||||
query: (editor) =>
|
||||
!editor.api.some({
|
||||
match: { type: editor.getType(KEYS.codeBlock) },
|
||||
}),
|
||||
})
|
||||
),
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
BlockquotePlugin,
|
||||
H1Plugin,
|
||||
H2Plugin,
|
||||
H3Plugin,
|
||||
H4Plugin,
|
||||
H5Plugin,
|
||||
H6Plugin,
|
||||
HorizontalRulePlugin,
|
||||
} from "@platejs/basic-nodes/react";
|
||||
import { ParagraphPlugin } from "platejs/react";
|
||||
|
||||
import { BlockquoteElement } from "@/components/ui/blockquote-node";
|
||||
import {
|
||||
H1Element,
|
||||
H2Element,
|
||||
H3Element,
|
||||
H4Element,
|
||||
H5Element,
|
||||
H6Element,
|
||||
} from "@/components/ui/heading-node";
|
||||
import { HrElement } from "@/components/ui/hr-node";
|
||||
import { ParagraphElement } from "@/components/ui/paragraph-node";
|
||||
|
||||
export const BasicBlocksKit = [
|
||||
ParagraphPlugin.withComponent(ParagraphElement),
|
||||
H1Plugin.configure({
|
||||
node: {
|
||||
component: H1Element,
|
||||
},
|
||||
rules: {
|
||||
break: { empty: "reset" },
|
||||
},
|
||||
shortcuts: { toggle: { keys: "mod+alt+1" } },
|
||||
}),
|
||||
H2Plugin.configure({
|
||||
node: {
|
||||
component: H2Element,
|
||||
},
|
||||
rules: {
|
||||
break: { empty: "reset" },
|
||||
},
|
||||
shortcuts: { toggle: { keys: "mod+alt+2" } },
|
||||
}),
|
||||
H3Plugin.configure({
|
||||
node: {
|
||||
component: H3Element,
|
||||
},
|
||||
rules: {
|
||||
break: { empty: "reset" },
|
||||
},
|
||||
shortcuts: { toggle: { keys: "mod+alt+3" } },
|
||||
}),
|
||||
H4Plugin.configure({
|
||||
node: {
|
||||
component: H4Element,
|
||||
},
|
||||
rules: {
|
||||
break: { empty: "reset" },
|
||||
},
|
||||
shortcuts: { toggle: { keys: "mod+alt+4" } },
|
||||
}),
|
||||
H5Plugin.configure({
|
||||
node: {
|
||||
component: H5Element,
|
||||
},
|
||||
rules: {
|
||||
break: { empty: "reset" },
|
||||
},
|
||||
}),
|
||||
H6Plugin.configure({
|
||||
node: {
|
||||
component: H6Element,
|
||||
},
|
||||
rules: {
|
||||
break: { empty: "reset" },
|
||||
},
|
||||
}),
|
||||
BlockquotePlugin.configure({
|
||||
node: { component: BlockquoteElement },
|
||||
shortcuts: { toggle: { keys: "mod+shift+period" } },
|
||||
}),
|
||||
HorizontalRulePlugin.withComponent(HrElement),
|
||||
];
|
||||
@@ -0,0 +1,38 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
BoldPlugin,
|
||||
CodePlugin,
|
||||
HighlightPlugin,
|
||||
ItalicPlugin,
|
||||
StrikethroughPlugin,
|
||||
SubscriptPlugin,
|
||||
SuperscriptPlugin,
|
||||
UnderlinePlugin,
|
||||
} from "@platejs/basic-nodes/react";
|
||||
|
||||
import { CodeLeaf } from "@/components/ui/code-node";
|
||||
import { HighlightLeaf } from "@/components/ui/highlight-node";
|
||||
|
||||
export const BasicMarksKit = [
|
||||
BoldPlugin,
|
||||
ItalicPlugin,
|
||||
UnderlinePlugin,
|
||||
CodePlugin.configure({
|
||||
node: { component: CodeLeaf },
|
||||
shortcuts: { toggle: { keys: "mod+e" } },
|
||||
}),
|
||||
StrikethroughPlugin.configure({
|
||||
shortcuts: { toggle: { keys: "mod+shift+x" } },
|
||||
}),
|
||||
SubscriptPlugin.configure({
|
||||
shortcuts: { toggle: { keys: "mod+comma" } },
|
||||
}),
|
||||
SuperscriptPlugin.configure({
|
||||
shortcuts: { toggle: { keys: "mod+period" } },
|
||||
}),
|
||||
HighlightPlugin.configure({
|
||||
node: { component: HighlightLeaf },
|
||||
shortcuts: { toggle: { keys: "mod+shift+h" } },
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { BasicBlocksKit } from "./basic-blocks-kit";
|
||||
import { BasicMarksKit } from "./basic-marks-kit";
|
||||
|
||||
export const BasicNodesKit = [...BasicBlocksKit, ...BasicMarksKit];
|
||||
@@ -0,0 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { CalloutPlugin } from "@platejs/callout/react";
|
||||
|
||||
import { CalloutElement } from "@/components/ui/callout-node";
|
||||
|
||||
export const CalloutKit = [CalloutPlugin.withComponent(CalloutElement)];
|
||||
@@ -0,0 +1,199 @@
|
||||
"use client";
|
||||
|
||||
import { type Descendant, KEYS } from "platejs";
|
||||
import { createPlatePlugin, type PlateElementProps } from "platejs/react";
|
||||
import type { FC } from "react";
|
||||
import { InlineCitation, UrlCitation } from "@/components/assistant-ui/inline-citation";
|
||||
import {
|
||||
CITATION_REGEX,
|
||||
type CitationUrlMap,
|
||||
parseTextWithCitations,
|
||||
} from "@/lib/citations/citation-parser";
|
||||
|
||||
/**
|
||||
* Plate inline-void node for one `[citation:...]` reference.
|
||||
* Inline voids keep the citation chip atomic while preserving caret behavior
|
||||
* around the surrounding text.
|
||||
*/
|
||||
export type CitationElementNode = {
|
||||
type: "citation";
|
||||
kind: "chunk" | "doc" | "url";
|
||||
chunkId?: number;
|
||||
url?: string;
|
||||
/** Original literal token that produced this citation node. */
|
||||
rawText: string;
|
||||
children: [{ text: "" }];
|
||||
};
|
||||
|
||||
const CITATION_TYPE = "citation";
|
||||
|
||||
const CitationElement: FC<PlateElementProps<CitationElementNode>> = ({
|
||||
attributes,
|
||||
children,
|
||||
element,
|
||||
}) => {
|
||||
const isUrl = element.kind === "url";
|
||||
return (
|
||||
<span {...attributes} className="inline-flex align-baseline">
|
||||
<span contentEditable={false}>
|
||||
{isUrl && element.url ? (
|
||||
<UrlCitation url={element.url} />
|
||||
) : element.chunkId !== undefined ? (
|
||||
<InlineCitation chunkId={element.chunkId} isDocsChunk={element.kind === "doc"} />
|
||||
) : null}
|
||||
</span>
|
||||
{children}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const CitationPlugin = createPlatePlugin({
|
||||
key: CITATION_TYPE,
|
||||
node: {
|
||||
isElement: true,
|
||||
isInline: true,
|
||||
isVoid: true,
|
||||
type: CITATION_TYPE,
|
||||
component: CitationElement,
|
||||
},
|
||||
});
|
||||
|
||||
export const CitationKit = [CitationPlugin];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Slate value transform
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Local structural shapes keep the recursive walker readable without forcing
|
||||
// Plate's broad Descendant union into narrower generic predicates.
|
||||
type SlateText = { text: string } & Record<string, unknown>;
|
||||
type SlateElement = { type?: string; children: Descendant[] } & Record<string, unknown>;
|
||||
|
||||
function isText(node: Descendant): boolean {
|
||||
return typeof (node as { text?: unknown }).text === "string";
|
||||
}
|
||||
|
||||
function asText(node: Descendant): SlateText {
|
||||
return node as unknown as SlateText;
|
||||
}
|
||||
|
||||
function asElement(node: Descendant): SlateElement {
|
||||
return node as unknown as SlateElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtrees that should keep citation tokens as text:
|
||||
* - Code nodes preserve source text and reject inline void children.
|
||||
* - Link nodes already render as anchors; citation chips are interactive
|
||||
* shadcn Button-based controls, so injecting them would nest interactions.
|
||||
*/
|
||||
const SKIP_SUBTREE_TYPES = new Set<string>([KEYS.codeBlock, "code_line", KEYS.link]);
|
||||
|
||||
/**
|
||||
* Preserve text marks such as bold and italic when splitting around citations.
|
||||
*/
|
||||
function copyMarks(textNode: SlateText): Record<string, unknown> {
|
||||
const { text: _text, ...marks } = textNode;
|
||||
return marks;
|
||||
}
|
||||
|
||||
function makeCitationElement(
|
||||
rawText: string,
|
||||
segment: { kind: "url"; url: string } | { kind: "chunk"; chunkId: number; isDocsChunk: boolean }
|
||||
): CitationElementNode {
|
||||
if (segment.kind === "url") {
|
||||
return {
|
||||
type: CITATION_TYPE,
|
||||
kind: "url",
|
||||
url: segment.url,
|
||||
rawText,
|
||||
children: [{ text: "" }],
|
||||
};
|
||||
}
|
||||
return {
|
||||
type: CITATION_TYPE,
|
||||
kind: segment.isDocsChunk ? "doc" : "chunk",
|
||||
chunkId: segment.chunkId,
|
||||
rawText,
|
||||
children: [{ text: "" }],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep each original citation token on the generated node for diagnostics.
|
||||
*/
|
||||
function extractRawCitationMatches(text: string): string[] {
|
||||
const matches: string[] = [];
|
||||
CITATION_REGEX.lastIndex = 0;
|
||||
let m: RegExpExecArray | null = CITATION_REGEX.exec(text);
|
||||
while (m !== null) {
|
||||
matches.push(m[0]);
|
||||
m = CITATION_REGEX.exec(text);
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
function transformTextNode(node: SlateText, urlMap: CitationUrlMap): Descendant[] {
|
||||
const segments = parseTextWithCitations(node.text, urlMap);
|
||||
if (segments.length === 1 && typeof segments[0] === "string") {
|
||||
return [node as unknown as Descendant];
|
||||
}
|
||||
|
||||
const marks = copyMarks(node);
|
||||
const rawMatches = extractRawCitationMatches(node.text);
|
||||
const out: Descendant[] = [];
|
||||
let citationIdx = 0;
|
||||
let pendingText: string | null = null;
|
||||
|
||||
const flushText = () => {
|
||||
// Inline voids need text siblings, even at text boundaries.
|
||||
out.push({ ...marks, text: pendingText ?? "" } as unknown as Descendant);
|
||||
pendingText = null;
|
||||
};
|
||||
|
||||
for (const segment of segments) {
|
||||
if (typeof segment === "string") {
|
||||
pendingText = (pendingText ?? "") + segment;
|
||||
} else {
|
||||
flushText();
|
||||
const raw = rawMatches[citationIdx] ?? "";
|
||||
out.push(makeCitationElement(raw, segment) as unknown as Descendant);
|
||||
citationIdx += 1;
|
||||
// Ensure a trailing text sibling if the citation ends the node.
|
||||
pendingText = "";
|
||||
}
|
||||
}
|
||||
flushText();
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function transformChildren(children: Descendant[], urlMap: CitationUrlMap): Descendant[] {
|
||||
const out: Descendant[] = [];
|
||||
for (const child of children) {
|
||||
if (isText(child)) {
|
||||
out.push(...transformTextNode(asText(child), urlMap));
|
||||
continue;
|
||||
}
|
||||
const elementChild = asElement(child);
|
||||
const elementType = (elementChild.type ?? "") as string;
|
||||
if (elementType && SKIP_SUBTREE_TYPES.has(elementType)) {
|
||||
out.push(child);
|
||||
continue;
|
||||
}
|
||||
out.push({
|
||||
...elementChild,
|
||||
children: transformChildren(elementChild.children, urlMap),
|
||||
} as unknown as Descendant);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace citation tokens in a deserialized Slate tree with citation inline
|
||||
* void nodes. URL placeholders from `preprocessCitationMarkdown` are resolved
|
||||
* through `urlMap`; skipped subtrees are returned unchanged.
|
||||
*/
|
||||
export function injectCitationNodes(value: Descendant[], urlMap: CitationUrlMap): Descendant[] {
|
||||
return transformChildren(value, urlMap);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from "@platejs/code-block/react";
|
||||
import { all, createLowlight } from "lowlight";
|
||||
|
||||
import { CodeBlockElement, CodeLineElement, CodeSyntaxLeaf } from "@/components/ui/code-block-node";
|
||||
|
||||
const lowlight = createLowlight(all);
|
||||
|
||||
export const CodeBlockKit = [
|
||||
CodeBlockPlugin.configure({
|
||||
node: { component: CodeBlockElement },
|
||||
options: { lowlight },
|
||||
shortcuts: { toggle: { keys: "mod+alt+8" } },
|
||||
}),
|
||||
CodeLinePlugin.withComponent(CodeLineElement),
|
||||
CodeSyntaxPlugin.withComponent(CodeSyntaxLeaf),
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { DndPlugin } from "@platejs/dnd";
|
||||
import { DndProvider } from "react-dnd";
|
||||
import { HTML5Backend } from "react-dnd-html5-backend";
|
||||
|
||||
import { BlockDraggable } from "@/components/ui/block-draggable";
|
||||
|
||||
export const DndKit = [
|
||||
DndPlugin.configure({
|
||||
options: {
|
||||
enableScroller: true,
|
||||
},
|
||||
render: {
|
||||
aboveNodes: BlockDraggable,
|
||||
aboveSlate: ({ children }) => <DndProvider backend={HTML5Backend}>{children}</DndProvider>,
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { createPlatePlugin, useEditorReadOnly } from "platejs/react";
|
||||
|
||||
import { useEditorSave } from "@/components/editor/editor-save-context";
|
||||
import { FixedToolbar } from "@/components/ui/fixed-toolbar";
|
||||
import { FixedToolbarButtons } from "@/components/ui/fixed-toolbar-buttons";
|
||||
|
||||
function ConditionalFixedToolbar() {
|
||||
const readOnly = useEditorReadOnly();
|
||||
const { onSave, hasUnsavedChanges, canToggleMode, reserveToolbarSpace } = useEditorSave();
|
||||
|
||||
const hasVisibleControls =
|
||||
!readOnly || canToggleMode || (!!onSave && hasUnsavedChanges && !readOnly);
|
||||
|
||||
if (!hasVisibleControls) {
|
||||
if (!reserveToolbarSpace) return null;
|
||||
return (
|
||||
<FixedToolbar className="pointer-events-none opacity-0">
|
||||
<div className="h-8 w-full" />
|
||||
</FixedToolbar>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<FixedToolbar>
|
||||
<FixedToolbarButtons />
|
||||
</FixedToolbar>
|
||||
);
|
||||
}
|
||||
|
||||
export const FixedToolbarKit = [
|
||||
createPlatePlugin({
|
||||
key: "fixed-toolbar",
|
||||
render: {
|
||||
beforeEditable: () => <ConditionalFixedToolbar />,
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { createPlatePlugin } from "platejs/react";
|
||||
|
||||
import { FloatingToolbar } from "@/components/ui/floating-toolbar";
|
||||
import { FloatingToolbarButtons } from "@/components/ui/floating-toolbar-buttons";
|
||||
|
||||
export const FloatingToolbarKit = [
|
||||
createPlatePlugin({
|
||||
key: "floating-toolbar",
|
||||
render: {
|
||||
afterEditable: () => (
|
||||
<FloatingToolbar>
|
||||
<FloatingToolbarButtons />
|
||||
</FloatingToolbar>
|
||||
),
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { IndentPlugin } from "@platejs/indent/react";
|
||||
import { KEYS } from "platejs";
|
||||
|
||||
export const IndentKit = [
|
||||
IndentPlugin.configure({
|
||||
inject: {
|
||||
targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock, KEYS.toggle],
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
"use client";
|
||||
|
||||
import { LinkPlugin } from "@platejs/link/react";
|
||||
|
||||
import { LinkElement } from "@/components/ui/link-node";
|
||||
import { LinkFloatingToolbar } from "@/components/ui/link-toolbar";
|
||||
|
||||
export const LinkKit = [
|
||||
LinkPlugin.configure({
|
||||
render: {
|
||||
node: LinkElement,
|
||||
afterEditable: () => <LinkFloatingToolbar />,
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import { ListPlugin } from "@platejs/list/react";
|
||||
import { KEYS } from "platejs";
|
||||
|
||||
import { IndentKit } from "@/components/editor/plugins/indent-kit";
|
||||
import { BlockList } from "@/components/ui/block-list";
|
||||
|
||||
export const ListKit = [
|
||||
...IndentKit,
|
||||
ListPlugin.configure({
|
||||
inject: {
|
||||
targetPlugins: [...KEYS.heading, KEYS.p, KEYS.blockquote, KEYS.codeBlock, KEYS.toggle],
|
||||
},
|
||||
render: {
|
||||
belowNodes: BlockList,
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { EquationPlugin, InlineEquationPlugin } from "@platejs/math/react";
|
||||
|
||||
import { EquationElement, InlineEquationElement } from "@/components/ui/equation-node";
|
||||
|
||||
export const MathKit = [
|
||||
EquationPlugin.withComponent(EquationElement),
|
||||
InlineEquationPlugin.withComponent(InlineEquationElement),
|
||||
];
|
||||
@@ -0,0 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import { BlockSelectionPlugin } from "@platejs/selection/react";
|
||||
|
||||
import { BlockSelection } from "@/components/ui/block-selection";
|
||||
|
||||
export const SelectionKit = [
|
||||
BlockSelectionPlugin.configure({
|
||||
render: {
|
||||
belowRootNodes: BlockSelection as any,
|
||||
},
|
||||
options: {
|
||||
isSelectable: (element) => {
|
||||
// Exclude specific block types from selection
|
||||
if (["code_line", "td", "th"].includes(element.type as string)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
},
|
||||
}),
|
||||
];
|
||||
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { SlashInputPlugin, SlashPlugin } from "@platejs/slash-command/react";
|
||||
import { KEYS } from "platejs";
|
||||
|
||||
import { SlashInputElement } from "@/components/ui/slash-node";
|
||||
|
||||
export const SlashCommandKit = [
|
||||
SlashPlugin.configure({
|
||||
options: {
|
||||
trigger: "/",
|
||||
triggerPreviousCharPattern: /^\s?$/,
|
||||
triggerQuery: (editor) =>
|
||||
!editor.api.some({
|
||||
match: { type: editor.getType(KEYS.codeBlock) },
|
||||
}),
|
||||
},
|
||||
}),
|
||||
SlashInputPlugin.withComponent(SlashInputElement),
|
||||
];
|
||||
@@ -0,0 +1,22 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
TableCellHeaderPlugin,
|
||||
TableCellPlugin,
|
||||
TablePlugin,
|
||||
TableRowPlugin,
|
||||
} from "@platejs/table/react";
|
||||
|
||||
import {
|
||||
TableCellElement,
|
||||
TableCellHeaderElement,
|
||||
TableElement,
|
||||
TableRowElement,
|
||||
} from "@/components/ui/table-node";
|
||||
|
||||
export const TableKit = [
|
||||
TablePlugin.withComponent(TableElement),
|
||||
TableRowPlugin.withComponent(TableRowElement),
|
||||
TableCellPlugin.withComponent(TableCellElement),
|
||||
TableCellHeaderPlugin.withComponent(TableCellHeaderElement),
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { TogglePlugin } from "@platejs/toggle/react";
|
||||
|
||||
import { ToggleElement } from "@/components/ui/toggle-node";
|
||||
|
||||
export const ToggleKit = [
|
||||
TogglePlugin.configure({
|
||||
node: { component: ToggleElement },
|
||||
shortcuts: { toggle: { keys: "mod+alt+9" } },
|
||||
}),
|
||||
];
|
||||
Reference in New Issue
Block a user