All checks were successful
CI / Auth e2e pack (push) Successful in 4m10s
CI / Import/export fidelity gate (push) Successful in 45s
CD / Build and push images (push) Successful in 1m0s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m8s
CD / Promote to Int (push) Successful in 10s
CI / Lint, typecheck, test (push) Successful in 2m48s
CI / Build container images (push) Has been skipped
Two corrective changes:
- Register the `section` node in the web TipTap editor (with
wrapInSection/unwrapSection commands) so the editor schema matches the
shared editorSchema again. The schema-drift guard
(document-extensions.test) rightly failed after 2e96173 added `section`
to the shared schema without the editor side — this restores it.
- Replace the ineffective warm-up gate with a serial chain of the pnpm
CI jobs (checks -> auth-e2e -> fidelity). Warming the shared action
cache did not help: the dependent jobs still started together and
corrupted the cache during concurrent extraction. Serializing them is
what actually prevents it; CD image builds still run in parallel.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
211 lines
5.5 KiB
TypeScript
211 lines
5.5 KiB
TypeScript
import { Node, nodeInputRule, textblockTypeInputRule, wrappingInputRule } from '@tiptap/core';
|
|
|
|
import { attributesFromSpec, nodeSpec, passthroughNodeIO } from '../spec-utils';
|
|
|
|
declare module '@tiptap/core' {
|
|
interface Commands<ReturnType> {
|
|
documentTextBasics: {
|
|
setParagraph: () => ReturnType;
|
|
setHeading: (level: 1 | 2 | 3 | 4) => ReturnType;
|
|
toggleHeading: (level: 1 | 2 | 3 | 4) => ReturnType;
|
|
toggleBlockquote: () => ReturnType;
|
|
toggleCodeBlock: () => ReturnType;
|
|
setHorizontalRule: () => ReturnType;
|
|
setHardBreak: () => ReturnType;
|
|
};
|
|
documentSection: {
|
|
/** Wrap the selection in a styled section (issue #75). */
|
|
wrapInSection: (attrs: { pluginId: string; styleId: string }) => ReturnType;
|
|
/** Unwrap the nearest section, leaving its content in place. */
|
|
unwrapSection: () => ReturnType;
|
|
};
|
|
}
|
|
}
|
|
|
|
/** ProseMirror's document root; `topNode` is a schema-construction option,
|
|
* not part of the NodeSpec, so it is not derived from `nodeSpec('doc')`. */
|
|
export const Doc = Node.create({
|
|
name: 'doc',
|
|
topNode: true,
|
|
content: nodeSpec('doc').content,
|
|
});
|
|
|
|
export const Text = Node.create({
|
|
name: 'text',
|
|
group: nodeSpec('text').group,
|
|
});
|
|
|
|
const paragraphSpec = nodeSpec('paragraph');
|
|
export const Paragraph = Node.create({
|
|
name: 'paragraph',
|
|
group: paragraphSpec.group,
|
|
content: paragraphSpec.content,
|
|
...passthroughNodeIO(paragraphSpec),
|
|
addCommands() {
|
|
return {
|
|
setParagraph:
|
|
() =>
|
|
({ commands }) =>
|
|
commands.setNode(this.name),
|
|
};
|
|
},
|
|
addKeyboardShortcuts() {
|
|
return { 'Mod-Alt-0': () => this.editor.commands.setParagraph() };
|
|
},
|
|
});
|
|
|
|
const headingSpec = nodeSpec('heading');
|
|
const HEADING_LEVELS = [1, 2, 3, 4] as const;
|
|
export const Heading = Node.create({
|
|
name: 'heading',
|
|
group: headingSpec.group,
|
|
content: headingSpec.content,
|
|
defining: headingSpec.defining,
|
|
addAttributes() {
|
|
return attributesFromSpec(headingSpec);
|
|
},
|
|
...passthroughNodeIO(headingSpec),
|
|
addCommands() {
|
|
return {
|
|
setHeading:
|
|
(level: 1 | 2 | 3 | 4) =>
|
|
({ commands }) =>
|
|
commands.setNode(this.name, { level }),
|
|
toggleHeading:
|
|
(level: 1 | 2 | 3 | 4) =>
|
|
({ commands }) =>
|
|
commands.toggleNode(this.name, 'paragraph', { level }),
|
|
};
|
|
},
|
|
addKeyboardShortcuts() {
|
|
return Object.fromEntries(
|
|
HEADING_LEVELS.map((level) => [
|
|
`Mod-Alt-${level}`,
|
|
() => this.editor.commands.toggleHeading(level),
|
|
]),
|
|
);
|
|
},
|
|
addInputRules() {
|
|
return HEADING_LEVELS.map((level) =>
|
|
textblockTypeInputRule({
|
|
find: new RegExp(`^(#{${level}})\\s$`),
|
|
type: this.type,
|
|
getAttributes: { level },
|
|
}),
|
|
);
|
|
},
|
|
});
|
|
|
|
const blockquoteSpec = nodeSpec('blockquote');
|
|
export const Blockquote = Node.create({
|
|
name: 'blockquote',
|
|
group: blockquoteSpec.group,
|
|
content: blockquoteSpec.content,
|
|
...passthroughNodeIO(blockquoteSpec),
|
|
addCommands() {
|
|
return {
|
|
toggleBlockquote:
|
|
() =>
|
|
({ commands }) =>
|
|
commands.toggleWrap(this.name),
|
|
};
|
|
},
|
|
addKeyboardShortcuts() {
|
|
return { 'Mod-Shift-b': () => this.editor.commands.toggleBlockquote() };
|
|
},
|
|
addInputRules() {
|
|
return [wrappingInputRule({ find: /^\s*>\s$/, type: this.type })];
|
|
},
|
|
});
|
|
|
|
const sectionSpec = nodeSpec('section');
|
|
export const Section = Node.create({
|
|
name: 'section',
|
|
group: sectionSpec.group,
|
|
content: sectionSpec.content,
|
|
defining: sectionSpec.defining,
|
|
addAttributes() {
|
|
return attributesFromSpec(sectionSpec);
|
|
},
|
|
...passthroughNodeIO(sectionSpec),
|
|
addCommands() {
|
|
return {
|
|
wrapInSection:
|
|
(attrs) =>
|
|
({ commands }) =>
|
|
commands.wrapIn(this.name, attrs),
|
|
unwrapSection:
|
|
() =>
|
|
({ commands }) =>
|
|
commands.lift(this.name),
|
|
};
|
|
},
|
|
});
|
|
|
|
const codeBlockSpec = nodeSpec('code_block');
|
|
export const CodeBlock = Node.create({
|
|
name: 'code_block',
|
|
group: codeBlockSpec.group,
|
|
content: codeBlockSpec.content,
|
|
marks: codeBlockSpec.marks,
|
|
code: codeBlockSpec.code,
|
|
defining: codeBlockSpec.defining,
|
|
whitespace: codeBlockSpec.whitespace,
|
|
...passthroughNodeIO(codeBlockSpec),
|
|
addCommands() {
|
|
return {
|
|
toggleCodeBlock:
|
|
() =>
|
|
({ commands }) =>
|
|
commands.toggleNode(this.name, 'paragraph'),
|
|
};
|
|
},
|
|
addKeyboardShortcuts() {
|
|
return { 'Mod-Alt-c': () => this.editor.commands.toggleCodeBlock() };
|
|
},
|
|
addInputRules() {
|
|
return [textblockTypeInputRule({ find: /^```$/, type: this.type })];
|
|
},
|
|
});
|
|
|
|
const horizontalRuleSpec = nodeSpec('horizontal_rule');
|
|
export const HorizontalRule = Node.create({
|
|
name: 'horizontal_rule',
|
|
group: horizontalRuleSpec.group,
|
|
...passthroughNodeIO(horizontalRuleSpec),
|
|
addCommands() {
|
|
return {
|
|
setHorizontalRule:
|
|
() =>
|
|
({ chain }) =>
|
|
chain().insertContent({ type: this.name }).run(),
|
|
};
|
|
},
|
|
addInputRules() {
|
|
return [nodeInputRule({ find: /^---$/, type: this.type })];
|
|
},
|
|
});
|
|
|
|
const hardBreakSpec = nodeSpec('hard_break');
|
|
export const HardBreak = Node.create({
|
|
name: 'hard_break',
|
|
group: hardBreakSpec.group,
|
|
inline: hardBreakSpec.inline,
|
|
selectable: hardBreakSpec.selectable,
|
|
...passthroughNodeIO(hardBreakSpec),
|
|
addCommands() {
|
|
return {
|
|
setHardBreak:
|
|
() =>
|
|
({ chain }) =>
|
|
chain().insertContent({ type: this.name }).run(),
|
|
};
|
|
},
|
|
addKeyboardShortcuts() {
|
|
return {
|
|
'Shift-Enter': () => this.editor.commands.setHardBreak(),
|
|
'Mod-Enter': () => this.editor.commands.setHardBreak(),
|
|
};
|
|
},
|
|
});
|