Neuer Inline-Atom date_marker {kind: due|start, date: ISO}. Markdown
kanonisch ISO (>>2026-12-31), Eingabe-Kulanz dd.mm.yyyy; Block-Guard
vor blockquote hält zeilenführende >>Daten aus dem Zitat-Parser;
ungültige Kalenderdaten bleiben Text. Editor: InputRule beim Tippen
(+Leerzeichen), Anzeige per Intl.DateTimeFormat in Nutzersprache,
Überfällig-Färbung. Die User.locale-Verdrahtung existierte bereits
(auth-context, #17) — keine Änderung nötig. 6 Unit-Tests inkl.
Task-Listen-Zeile mit Marker und Mention.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155v2aT8AG1kZDQEZiCLBWC
74 lines
2.7 KiB
TypeScript
74 lines
2.7 KiB
TypeScript
import { Node } from 'prosemirror-model';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import { docToHtml } from './html';
|
|
import { docToMarkdown, markdownToDoc } from './markdown';
|
|
import { docToPlainText } from './plain-text';
|
|
|
|
function markers(doc: Node): { kind: string; date: string }[] {
|
|
const found: { kind: string; date: string }[] = [];
|
|
doc.descendants((node) => {
|
|
if (node.type.name === 'date_marker') {
|
|
found.push({ kind: node.attrs.kind as string, date: node.attrs.date as string });
|
|
}
|
|
});
|
|
return found;
|
|
}
|
|
|
|
describe('date markers >>/<< (issue #152)', () => {
|
|
it('parses ISO due and start dates and round-trips canonically', () => {
|
|
const doc = markdownToDoc('Projekt <<2026-07-01 bis >>2026-12-31 fertig.');
|
|
expect(markers(doc)).toEqual([
|
|
{ kind: 'start', date: '2026-07-01' },
|
|
{ kind: 'due', date: '2026-12-31' },
|
|
]);
|
|
const markdown = docToMarkdown(doc);
|
|
expect(markdown).toContain('<<2026-07-01');
|
|
expect(markdown).toContain('>>2026-12-31');
|
|
});
|
|
|
|
it('accepts dd.mm.yyyy as input lenience but serializes ISO', () => {
|
|
const doc = markdownToDoc('Zieltermin >>31.12.2026 bitte.');
|
|
expect(markers(doc)).toEqual([{ kind: 'due', date: '2026-12-31' }]);
|
|
expect(docToMarkdown(doc)).toContain('>>2026-12-31');
|
|
});
|
|
|
|
it('keeps a line-leading >>date out of blockquote parsing', () => {
|
|
const doc = markdownToDoc('>>2026-12-31 ist die Deadline.');
|
|
expect(markers(doc)).toEqual([{ kind: 'due', date: '2026-12-31' }]);
|
|
let quotes = 0;
|
|
doc.descendants((node) => {
|
|
if (node.type.name === 'blockquote') quotes += 1;
|
|
});
|
|
expect(quotes).toBe(0);
|
|
// A real blockquote still works.
|
|
const quote = markdownToDoc('> ein Zitat');
|
|
let realQuotes = 0;
|
|
quote.descendants((node) => {
|
|
if (node.type.name === 'blockquote') realQuotes += 1;
|
|
});
|
|
expect(realQuotes).toBe(1);
|
|
});
|
|
|
|
it('leaves invalid calendar dates as plain text', () => {
|
|
const doc = markdownToDoc('Kaputt: >>31.02.2026 bleibt Text.');
|
|
expect(markers(doc)).toEqual([]);
|
|
expect(docToMarkdown(doc)).toContain('31.02.2026');
|
|
});
|
|
|
|
it('renders HTML with kind, ISO date, and shows up in plain text', () => {
|
|
const doc = markdownToDoc('Bis >>2026-12-31.');
|
|
const html = docToHtml(doc);
|
|
expect(html).toContain('data-date-marker="due"');
|
|
expect(html).toContain('data-date="2026-12-31"');
|
|
expect(docToPlainText(doc)).toContain('2026-12-31');
|
|
});
|
|
|
|
it('works inside task list lines', () => {
|
|
const doc = markdownToDoc('- [ ] Bühne buchen >>2026-08-01 @nadia');
|
|
expect(markers(doc)).toEqual([{ kind: 'due', date: '2026-08-01' }]);
|
|
const markdown = docToMarkdown(doc);
|
|
expect(markdown).toContain('- [ ] Bühne buchen >>2026-08-01 @nadia');
|
|
});
|
|
});
|