diff --git a/src/services/NoteCreator.test.ts b/src/services/NoteCreator.test.ts index a7d5d81..10c2139 100644 --- a/src/services/NoteCreator.test.ts +++ b/src/services/NoteCreator.test.ts @@ -96,6 +96,24 @@ describe('NoteCreator.deriveTitle', () => { expect(NoteCreator.deriveTitle('Meeting: Q1 "plan" #person', '#person')) .toBe('Meeting Q1 plan'); }); + + it('strips extra leading separator after task checkbox (iterative marker stripping)', () => { + // "- [ ] - content" — after stripping "- " and "[ ] ", another "- " remains + expect(NoteCreator.deriveTitle('- [ ] - some content #idea', '#idea')) + .toBe('some content'); + }); + + it('strips timestamp prefix from title in task line with timestamp', () => { + // "- [ ] - 12:30 - content" — the timestamp should not appear in the entity name + expect(NoteCreator.deriveTitle('- [ ] - 12:30 - some content #idea', '#idea')) + .toBe('some content'); + }); + + it('strips timestamp prefix when line has wikilink after timestamp', () => { + // "- [ ] - 12:30 - [[some text]] #idea" — entity name from wikilink text only + expect(NoteCreator.deriveTitle('- [ ] - 12:30 - [[some text]] #idea', '#idea')) + .toBe('some text'); + }); }); // --------------------------------------------------------------------------- diff --git a/src/services/NoteCreator.ts b/src/services/NoteCreator.ts index 3c775ae..dae865c 100644 --- a/src/services/NoteCreator.ts +++ b/src/services/NoteCreator.ts @@ -81,10 +81,20 @@ export class NoteCreator { // Unwrap wikilinks: [[Alice]] → Alice, [[Alice|Alias]] → Alice s = s.replace(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g, '$1').trim(); - // Strip leading list / task markers (same order as PatternMatcher) - s = s.replace(/^\d+[.)]\s*/, '').trim(); // "1. " / "1)" - s = s.replace(/^[-*+]\s*/, '').trim(); // "- " / "* " / "+ " - s = s.replace(/^\[[ xX]\]\s*/, '').trim(); // "[ ] " / "[x] " + // Strip leading list / task markers iteratively to handle nested formats + // such as "- [ ] - content" where a separator "- " follows the task marker. + let prev: string; + do { + prev = s; + s = s.replace(/^\d+[.)]\s*/, '').trim(); // "1. " / "1)" + s = s.replace(/^[-*+]\s*/, '').trim(); // "- " / "* " / "+ " + s = s.replace(/^\[[ xX]\]\s*/, '').trim(); // "[ ] " / "[x] " + } while (s !== prev); + + // Strip a leading timestamp (e.g. "12:30" or "9:45") that may appear as a + // time annotation between list markers and the actual content, optionally + // followed by a separator dash ("12:30 - content" → "content"). + s = s.replace(/^\d{1,2}:\d{2}\s*[-–]?\s*/, '').trim(); s = s.replace(/\s+/g, ' ').trim(); @@ -103,10 +113,15 @@ export class NoteCreator { const re = NoteCreator.tagRegex(triggerTag); let s = lineText.replace(re, '$1').trim(); - // Strip leading list / task markers (same order as PatternMatcher) - s = s.replace(/^\d+[.)]\s*/, '').trim(); // "1. " / "1)" - s = s.replace(/^[-*+]\s*/, '').trim(); // "- " / "* " / "+ " - s = s.replace(/^\[[ xX]\]\s*/, '').trim(); // "[ ] " / "[x] " + // Strip leading list / task markers iteratively to handle nested formats + // such as "- [ ] - content" where a separator "- " follows the task marker. + let prev: string; + do { + prev = s; + s = s.replace(/^\d+[.)]\s*/, '').trim(); // "1. " / "1)" + s = s.replace(/^[-*+]\s*/, '').trim(); // "- " / "* " / "+ " + s = s.replace(/^\[[ xX]\]\s*/, '').trim(); // "[ ] " / "[x] " + } while (s !== prev); return s.replace(/\s+/g, ' ').trim(); } diff --git a/src/services/PatternMatcher.test.ts b/src/services/PatternMatcher.test.ts index 3178b4c..6391bbd 100644 --- a/src/services/PatternMatcher.test.ts +++ b/src/services/PatternMatcher.test.ts @@ -100,6 +100,18 @@ describe('PatternMatcher.match — Case 2 (full-line)', () => { it('line with plain text plus an embedded wikilink matches as full-line', () => { expect(pm.match('Met [[Sarah]] #person', [PERSON], CLEAR, allResolved)).toEqual(fullLine(PERSON)); }); + + it('resolved wikilink with only a timestamp prefix returns null — timestamp is not meaningful content', () => { + // "- [ ] - 12:30 - [[some text]] #idea" where the link is resolved: + // the only surrounding content is a timestamp, which is not a useful entity title + expect(pm.match('- [ ] - 12:30 - [[some text]] #idea', [IDEA], CLEAR, allResolved)).toBeNull(); + }); + + it('unresolved wikilink after timestamp in task item is detected as Case 1', () => { + // Even with a timestamp prefix, an unresolved wikilink must trigger Case 1 + expect(pm.match('- [ ] - 12:30 - [[some text]] #idea', [IDEA], CLEAR, noneResolved)) + .toEqual(unresolvedLink(IDEA, 'some text')); + }); }); // --- disabled --- diff --git a/src/services/PatternMatcher.ts b/src/services/PatternMatcher.ts index 37aa156..2295318 100644 --- a/src/services/PatternMatcher.ts +++ b/src/services/PatternMatcher.ts @@ -225,9 +225,21 @@ export class PatternMatcher { private hasMeaningfulContent(line: string, tag: string): boolean { let s = line.replace(this.tagRegex(tag), '$1').trim(); s = s.replace(/\[\[[^\]]*\]\]/g, '').trim(); // strip wikilinks - s = s.replace(/^\d+[.)]\s*/, '').trim(); // ordered list: "1. " or "1)" - s = s.replace(/^[-*+]\s*/, '').trim(); // unordered list: "- ", "* ", "+ " - s = s.replace(/^\[[ xX]\]\s*/, '').trim(); // task checkbox: "[ ] " or "[x] " + + // Strip leading list / task markers iteratively to handle nested formats + // such as "- [ ] - content" where a separator "- " follows the task marker. + let prev: string; + do { + prev = s; + s = s.replace(/^\d+[.)]\s*/, '').trim(); // ordered list: "1. " or "1)" + s = s.replace(/^[-*+]\s*/, '').trim(); // unordered list: "- ", "* ", "+ " + s = s.replace(/^\[[ xX]\]\s*/, '').trim(); // task checkbox: "[ ] " or "[x] " + } while (s !== prev); + + // A leading timestamp (e.g. "12:30") used as a time annotation between markers + // and content is not considered meaningful for deriving an entity title. + s = s.replace(/^\d{1,2}:\d{2}\s*[-–]?\s*/, '').trim(); + return s.length > 0; }