mirror of
https://github.com/vxcontrol/pentagi.git
synced 2026-09-27 04:35:35 +00:00
fix(editor): disable Underline + underscore/URL auto-format so typing matches load
Three StarterKit defaults corrupted content or diverged from the load path: - Underline (`underline: false`): its `++text++` markdown tokenizer collapsed the space in `C++ then C++` on load, and Ctrl+U emitted non-standard `++`. No underline semantics in our content, so drop it outright. - Bold/Italic underscore input+paste rules: typing `__init__`/`_word_` created emphasis (`**init**`/`*word*`) while the same text loaded stays literal (the marked layer neutralizes `_`), breaking identifiers on the typing path only (input rules don't run on paste/load). Extend StarterKit to drop the underscore rules (regex mentions `_`), keeping the `*`/`**` rules. - Link autolink/linkOnPaste (`false`): a typed/pasted bare URL now stays literal, matching load; explicit [text](url) and the toolbar still work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
18041520c3
commit
b7e4dea1a2
@@ -105,6 +105,15 @@ describe('literal backslashes survive — no doubling (regex classes, Windows pa
|
||||
});
|
||||
});
|
||||
|
||||
describe('Underline disabled — ++ never parses, C++/++flags prose survives', () => {
|
||||
it.each(['C++ then C++ again', 'a ++ b ++ c', 'compile ++flags++ here'])(
|
||||
'keeps %s byte-identical (no ++ collapse)',
|
||||
(s) => {
|
||||
expect(roundTrip(s)).toBe(s);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('bare URLs and emails stay literal — explicit [links] still work', () => {
|
||||
it.each(['see https://example.com/path now', 'contact me@example.com today', 'a <https://example.com> ref'])(
|
||||
'keeps %s byte-identical',
|
||||
@@ -240,3 +249,43 @@ describe('findVariableOccurrences — doc spans for the Available-variables cycl
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('typing matches load — underscore emphasis + bare-URL autolink disabled while typing', () => {
|
||||
const typeString = (input: string): { html: string; md: string } => {
|
||||
const editor = new Editor({ content: '', contentType: 'markdown', extensions: createMarkdownExtensions() });
|
||||
const { view } = editor;
|
||||
|
||||
for (const ch of input) {
|
||||
const { from } = view.state.selection;
|
||||
const handled = view.someProp('handleTextInput', (handler) => handler(view, from, from, ch));
|
||||
|
||||
if (!handled) {
|
||||
view.dispatch(view.state.tr.insertText(ch));
|
||||
}
|
||||
}
|
||||
|
||||
const result = { html: editor.getHTML(), md: editor.getMarkdown() };
|
||||
editor.destroy();
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
it('typed __dunder__ / _word_ stay literal (identifiers survive), matching load', () => {
|
||||
const { html, md } = typeString('call __init__ and _word_ done');
|
||||
|
||||
expect(html).not.toContain('<strong>');
|
||||
expect(html).not.toContain('<em>');
|
||||
expect(md).toContain('__init__');
|
||||
expect(md).toContain('_word_');
|
||||
});
|
||||
|
||||
it('typed bare URL stays literal — no autolink', () => {
|
||||
expect(typeString('see https://evil.example.com/x done').html).not.toContain('<a ');
|
||||
});
|
||||
|
||||
it('typed *italic* / **bold** / ~~strike~~ still convert (star + double-tilde kept)', () => {
|
||||
expect(typeString('a **bold** b').html).toContain('<strong>');
|
||||
expect(typeString('a *ital* b').html).toContain('<em>');
|
||||
expect(typeString('a ~~del~~ b').html).toContain('<s>');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,12 +9,46 @@ import { MarkdownPaste } from './editor-paste';
|
||||
import { TagHighlight } from './editor-tag-highlight';
|
||||
import { VariableHighlight } from './editor-variable-highlight';
|
||||
|
||||
// StarterKit's Bold/Italic register BOTH `**`/`*` and `__`/`_` input+paste rules. The marked layer keeps
|
||||
// `_`-emphasis literal on load/paste, so leaving the underscore TYPING rules on would diverge — typed
|
||||
// `__init__`/`_word_` would emphasize (→ `**init**`/`*word*`) while the same text loaded stays literal,
|
||||
// breaking identifiers. Drop only the underscore rules (their `find` regex mentions `_`; the `*` rules
|
||||
// stay) so typing matches load. (Link autolink + Underline are turned off in configure() below.)
|
||||
const StarterKitFaithful = StarterKit.extend({
|
||||
addExtensions() {
|
||||
return (this.parent?.() ?? []).map((extension) => {
|
||||
if (extension.name !== 'bold' && extension.name !== 'italic') {
|
||||
return extension;
|
||||
}
|
||||
|
||||
const dropUnderscore = (rules: { find: unknown }[]) =>
|
||||
rules.filter((rule) => !(rule.find instanceof RegExp && rule.find.source.includes('_')));
|
||||
|
||||
return extension.extend({
|
||||
addInputRules() {
|
||||
return dropUnderscore(this.parent?.() ?? []);
|
||||
},
|
||||
addPasteRules() {
|
||||
return dropUnderscore(this.parent?.() ?? []);
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Single source of truth for the editor's extension stack — shared by markdown-editor.tsx AND the
|
||||
// round-trip tests so they can never drift. createMarkdownLayer is the official @tiptap/markdown layer
|
||||
// tuned for our content (see editor-markdown.ts); VariableHighlight/TagHighlight are view-only decorations
|
||||
// ({{vars}} / <tags>) that don't affect serialization.
|
||||
// • underline: false — its `++text++` markdown corrupts `C++ … C++` prose on load and Ctrl+U emits `++`.
|
||||
// • link autolink/linkOnPaste: false — a typed/pasted bare URL stays literal (matches load); explicit
|
||||
// [text](url) and the toolbar link button still work.
|
||||
export const createMarkdownExtensions = (placeholder?: string) => [
|
||||
StarterKit.configure({ codeBlock: { HTMLAttributes: { class: 'hljs' } } }),
|
||||
StarterKitFaithful.configure({
|
||||
codeBlock: { HTMLAttributes: { class: 'hljs' } },
|
||||
link: { autolink: false, linkOnPaste: false },
|
||||
underline: false,
|
||||
}),
|
||||
MarkdownTable.configure({ resizable: true }),
|
||||
TableRow,
|
||||
TableHeader,
|
||||
|
||||
Reference in New Issue
Block a user