Skip to content

Commit 80c8d2b

Browse files
committed
Merge branch 'main' of https://github.com/NotePlan/plugins
2 parents 63d022c + 09a9c5d commit 80c8d2b

24 files changed

+1007
-229
lines changed

helpers/NPFrontMatter.js

Lines changed: 46 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,8 +1370,7 @@ export function analyzeTemplateStructure(templateData: string): {
13701370
inlineTitleText: string,
13711371
} {
13721372
try {
1373-
logDebug('analyzeTemplateStructure', `Analyzing template structure for template with ${templateData.length} characters`)
1374-
1373+
logDebug('analyzeTemplateStructure', `Analyzing template structure for template with ${templateData.length} characters, starting with "${templateData.substring(0, 20)}..."`)
13751374
// Initialize return object
13761375
const result = {
13771376
hasNewNoteTitle: false,
@@ -1397,6 +1396,7 @@ export function analyzeTemplateStructure(templateData: string): {
13971396
}
13981397
}
13991398
}
1399+
logDebug('analyzeTemplateStructure', `templateFrontmatterEnd: ${templateFrontmatterEnd}`)
14001400

14011401
if (templateFrontmatterEnd > 0) {
14021402
// Extract and parse template frontmatter
@@ -1435,29 +1435,39 @@ export function analyzeTemplateStructure(templateData: string): {
14351435
result.hasNewNoteTitle = 'newNoteTitle' in result.templateFrontmatter
14361436

14371437
// Check for output frontmatter in the body content
1438+
// Output frontmatter is ONLY valid if the body content STARTS with a separator (--- or --)
14381439
if (result.bodyContent) {
14391440
const bodyLines = result.bodyContent.split('\n')
1440-
1441-
// Find separator positions using helper function
1442-
const { startIndex: startBlock, endIndex: endBlock } = findSeparatorPositions(bodyLines)
1443-
1444-
// Only process as frontmatter if we actually found separator markers
1445-
if (startBlock >= 0 && endBlock >= 0) {
1446-
// Extract and parse output frontmatter
1447-
const { attributes, isValid } = extractAndParseFrontmatter(bodyLines, startBlock, endBlock)
1448-
1449-
if (isValid) {
1450-
result.outputFrontmatter = attributes
1451-
result.hasOutputFrontmatter = Object.keys(result.outputFrontmatter).length > 0
1452-
result.hasOutputTitle = 'title' in result.outputFrontmatter
1441+
const firstLine = bodyLines[0]?.trim() || ''
1442+
1443+
// Only look for output frontmatter if the first line is a separator
1444+
if (firstLine === '---' || firstLine === '--') {
1445+
// Find separator positions using helper function
1446+
const { startIndex: startBlock, endIndex: endBlock } = findSeparatorPositions(bodyLines)
1447+
1448+
// Only process as frontmatter if we actually found separator markers
1449+
if (startBlock >= 0 && endBlock >= 0) {
1450+
// Extract and parse output frontmatter
1451+
const { attributes, isValid } = extractAndParseFrontmatter(bodyLines, startBlock, endBlock)
1452+
1453+
if (isValid) {
1454+
result.outputFrontmatter = attributes
1455+
result.hasOutputFrontmatter = Object.keys(result.outputFrontmatter).length > 0
1456+
result.hasOutputTitle = 'title' in result.outputFrontmatter
1457+
} else {
1458+
// Not valid frontmatter, so no output frontmatter
1459+
result.outputFrontmatter = {}
1460+
result.hasOutputFrontmatter = false
1461+
result.hasOutputTitle = false
1462+
}
14531463
} else {
1454-
// Not valid frontmatter, so no output frontmatter
1464+
// No frontmatter separators found, so no output frontmatter
14551465
result.outputFrontmatter = {}
14561466
result.hasOutputFrontmatter = false
14571467
result.hasOutputTitle = false
14581468
}
14591469
} else {
1460-
// No frontmatter separators found, so no output frontmatter
1470+
// Body doesn't start with a separator, so no output frontmatter
14611471
result.outputFrontmatter = {}
14621472
result.hasOutputFrontmatter = false
14631473
result.hasOutputTitle = false
@@ -1732,7 +1742,7 @@ function findInlineTitleAfterFrontmatter(
17321742
}
17331743

17341744
/**
1735-
* Robust helper function to detect inline title in template body content
1745+
* Robust helper function to detect inline title in template body content (after first frontmatter is peeled off)
17361746
* Handles malformed frontmatter, multiple consecutive separators, and multiple frontmatter blocks
17371747
* @param {string} bodyContent - The template body content
17381748
* @returns {{hasInlineTitle: boolean, inlineTitleText: string}}
@@ -1746,9 +1756,24 @@ export function detectInlineTitle(bodyContent: string): { hasInlineTitle: boolea
17461756
const lines = bodyContent.split('\n')
17471757
logDebug('detectInlineTitle', `Processing ${lines.length} lines of rendered body content`)
17481758

1749-
// Find any note frontmatter blocks in the content
1750-
const frontmatterBlocks = findNoteFrontmatterBlock(lines)
1751-
logDebug('detectInlineTitle', `Found ${frontmatterBlocks.length} frontmatter blocks`)
1759+
// Check if body content starts with output frontmatter (--- or --)
1760+
// Output frontmatter is ONLY valid if it starts at the beginning of body content
1761+
const firstLine = lines[0]?.trim() || ''
1762+
let frontmatterBlocks = []
1763+
1764+
if (firstLine === '---' || firstLine === '--') {
1765+
// Find the output frontmatter block ONLY at the start
1766+
const { startIndex: startBlock, endIndex: endBlock } = findSeparatorPositions(lines, 0)
1767+
if (startBlock === 0 && endBlock >= 0) {
1768+
const { isValid } = extractAndParseFrontmatter(lines, startBlock, endBlock)
1769+
if (isValid) {
1770+
frontmatterBlocks = [{ startIndex: startBlock, endIndex: endBlock, isValid: true }]
1771+
logDebug('detectInlineTitle', `Found output frontmatter block at start: lines ${startBlock}-${endBlock}`)
1772+
}
1773+
}
1774+
}
1775+
1776+
logDebug('detectInlineTitle', `Found ${frontmatterBlocks.length} valid frontmatter blocks`)
17521777

17531778
// Find inline title after processing all frontmatter blocks
17541779
const result = findInlineTitleAfterFrontmatter(lines, frontmatterBlocks)

helpers/__tests__/NPFrontMatter.detectInlineTitleRobust.test.js

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -248,12 +248,8 @@ Content`
248248
})
249249

250250
describe('Real-world scenarios', () => {
251-
it('should handle meeting notes template', () => {
252-
const content = `---
253-
template: meeting
254-
category: work
255-
---
256-
--
251+
it('should handle template with output frontmatter and inline title', () => {
252+
const content = `--
257253
date: 2024-01-15
258254
attendees: John, Jane
259255
--
@@ -264,40 +260,6 @@ attendees: John, Jane
264260
const result = detectInlineTitle(content)
265261
expect(result).toEqual({ hasInlineTitle: true, inlineTitleText: 'Weekly Team Meeting' })
266262
})
267-
268-
it('should handle daily journal template', () => {
269-
const content = `---
270-
template: journal
271-
type: daily
272-
---
273-
--
274-
date: 2024-01-15
275-
mood: good
276-
--
277-
# January 15, 2024
278-
## What I accomplished today
279-
- Worked on project X
280-
- Had lunch with colleague`
281-
const result = detectInlineTitle(content)
282-
expect(result).toEqual({ hasInlineTitle: true, inlineTitleText: 'January 15, 2024' })
283-
})
284-
285-
it('should handle project notes template', () => {
286-
const content = `---
287-
template: project
288-
status: active
289-
---
290-
--
291-
project: MyApp
292-
version: 1.0
293-
--
294-
# Project Update: MyApp v1.0
295-
## Recent Changes
296-
- Fixed bug #123
297-
- Added new feature`
298-
const result = detectInlineTitle(content)
299-
expect(result).toEqual({ hasInlineTitle: true, inlineTitleText: 'Project Update: MyApp v1.0' })
300-
})
301263
})
302264

303265
describe('Error handling', () => {

helpers/__tests__/NPFrontMatter/NPFrontMatter.analyzeTemplateStructure.test.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -790,4 +790,72 @@ Some content here`
790790
expect(result).toBe('This is an H2 title')
791791
})
792792
})
793+
794+
describe('inline title before output frontmatter blocks', () => {
795+
test('should detect inline title when it comes before output frontmatter (bug case)', () => {
796+
const template = `---
797+
bg-color-dark:
798+
icon:
799+
icon-color:
800+
icon-style: regular
801+
area:
802+
category:
803+
topic:
804+
---
805+
# mytitle
806+
---
807+
⌫ unpublish: [[<%- date.now("YYYY-MM-DD", +28) %>]]
808+
⌫ archive: [[<%- date.now("YYYY-MM-DD", +28) %>]]
809+
---
810+
#### ✔︎ Tasks
811+
+ review: [[<%- date.now("YYYY-MM-DD", +1) %>]]
812+
---
813+
#### ✆ Connections
814+
+ No connections
815+
---
816+
#### ✪ Keywords
817+
+ No keywords
818+
---
819+
#### ⤷ Links
820+
+ No links
821+
---
822+
#### ✎ Notes
823+
+ No notes
824+
---
825+
#### ⁂ Details
826+
+ No details
827+
---
828+
#### ☰ References
829+
+ No references
830+
---`
831+
832+
const result = analyzeTemplateStructure(template)
833+
834+
// This test documents the current bug: inline title is NOT detected
835+
// when it appears before output frontmatter blocks
836+
expect(result.hasInlineTitle).toBe(true) // This will likely fail, showing the bug
837+
expect(result.inlineTitleText).toBe('mytitle')
838+
})
839+
840+
test('should use inline title when calling getNoteTitleFromTemplate', () => {
841+
const template = `---
842+
bg-color-dark:
843+
icon:
844+
icon-color:
845+
icon-style: regular
846+
area:
847+
category:
848+
topic:
849+
---
850+
# mytitle
851+
---
852+
⌫ unpublish: [[<%- date.now("YYYY-MM-DD", +28) %>]]
853+
⌫ archive: [[<%- date.now("YYYY-MM-DD", +28) %>]]
854+
---`
855+
856+
const result = getNoteTitleFromTemplate(template)
857+
858+
expect(result).toBe('mytitle')
859+
})
860+
})
793861
})

helpers/note.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,7 @@ export function calendarNotesSortedByChanged(): Array<TNote> {
388388
* @return {Array<TNote>} array of notes
389389
*/
390390
export function calendarNotesSortedByDate(includeFutureCalendarNotes: boolean = false, includeTeamspaceNotes: boolean = false): Array<TNote> {
391-
let notes = (includeFutureCalendarNotes)
392-
? DataStore.calendarNotes.slice()
393-
: pastCalendarNotes()
391+
let notes = includeFutureCalendarNotes ? DataStore.calendarNotes.slice() : pastCalendarNotes()
394392

395393
// Remove Teamspace calendar notes if requested
396394
if (!includeTeamspaceNotes) {
@@ -553,9 +551,14 @@ export function replaceSection(
553551
newSectionContent: string,
554552
): void {
555553
try {
554+
// $FlowIgnore
555+
const editorNote = note?.note
556+
const isEditor = editorNote !== undefined
556557
logDebug(
557558
'note / replaceSection',
558-
`Starting for note '${displayTitle(note)}'. Will remove '${headingOfSectionToReplace}' -> '${newSectionHeading}' level ${newSectionHeadingLevel}`,
559+
`Starting for note '${displayTitle(note)}' ${
560+
isEditor ? '(Editor)' : '(not in Editor)'
561+
}. Will remove '${headingOfSectionToReplace}' -> '${newSectionHeading}' level ${newSectionHeadingLevel}`,
559562
)
560563
// First remove existing heading (the start of the heading text will probably be right, but the end will probably need to be changed)
561564
const insertionLineIndex = removeSection(note, headingOfSectionToReplace)
@@ -826,13 +829,13 @@ export function numberOfOpenItemsInNote(note: CoreNoteFields): number {
826829
export function setIconForNote(note: TNote, icon: string, iconColor: ?string, iconStyle: ?string): void {
827830
// To set icon in frontmatter, first read existing frontmatter, then update.
828831
const noteFrontmatter = note.frontmatterAttributes
829-
noteFrontmatter["icon"] = icon
832+
noteFrontmatter['icon'] = icon
830833
if (iconColor) {
831-
noteFrontmatter["icon-color"] = iconColor
834+
noteFrontmatter['icon-color'] = iconColor
832835
}
833836
if (iconStyle) {
834-
noteFrontmatter["icon-style"] = iconStyle
837+
noteFrontmatter['icon-style'] = iconStyle
835838
}
836839
// $FlowIgnore[cannot-write] documentation says this particular usage *is* safe
837840
note.frontmatterAttributes = noteFrontmatter
838-
}
841+
}

helpers/userInput.js

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ export async function chooseFolder(
325325
alpha: 0.5,
326326
darkAlpha: 0.5,
327327
}
328-
logDebug('userInput / chooseFolder', `creating with folder path, starting at "${startFolder}"`)
328+
logDebug('userInput / chooseFolder', `starting with startFolder: ${startFolder ? `"${startFolder}"` : 'none (/)'}`)
329329

330330
// Get all folders, excluding @Trash
331331
// V2
@@ -346,9 +346,9 @@ export async function chooseFolder(
346346
if (folders.length > 0) {
347347
// Create folder options for display (without new folder option at this point)
348348
const [simpleFolderOptions, decoratedFolderOptions] = createFolderOptions(folders, teamspaceDefs, includeFolderPath)
349-
// for(let i = 0; i < 15; i++) {
350-
// console.log(`- ${i}: ${folders[i]}`)
351-
// }
349+
for (let i = 0; i < (folders.length > 15 ? 15 : folders.length); i++) {
350+
logDebug('userInput / chooseFolder', `- ${i}: ${folders[i]} simpleOption: ${simpleFolderOptions[i].label} decoratedOption: ${decoratedFolderOptions[i].text}`)
351+
}
352352

353353
// Get user selection. Use newer CommandBar.showOptions() from v3.18 if available.
354354
let result: TCommandBarOptionObject | any
@@ -366,6 +366,7 @@ export async function chooseFolder(
366366
// ✅ for folder creation to a Teamspace root opt-click
367367
// ✅ for folder creation to a Teamspace subfolder opt-click
368368

369+
let actualIndex = -1
369370
if (includeNewFolderOption) {
370371
// Add in the new folder option just for newer CommandBar use
371372
decoratedFolderOptions.unshift(addDecoratedNewFolderOption)
@@ -379,12 +380,20 @@ export async function chooseFolder(
379380
// i.e. new folder wanted, but no name given yet
380381
folder = ''
381382
newFolderWanted = true
383+
logDebug('userInput / chooseFolder CHOSE NEW FOLDER CREATE OPTION', `- result.index: ${result.index}`)
382384
} else if (optClickedOnFolder) {
385+
logDebug('userInput / chooseFolder CHOSEN OPT CLICKED ON FOLDER', `- optClickedOnFolder: true`)
383386
// i.e. new folder wanted, and parent folder chosen
384-
folder = folders[result.index - 1] // to ignore the added new folder option if present
387+
actualIndex = result.index - 1
388+
folder = folders[actualIndex] // to ignore the added new folder option if present
385389
newFolderWanted = true
386390
} else {
387-
folder = folders[result.index - 1] // to ignore the added new folder option if present
391+
actualIndex = result.index - 1
392+
folder = folders[actualIndex] // to ignore the added new folder option if present
393+
logDebug('userInput / chooseFolder CHOSEN FOLDER', `- decoratedFolderOptions[${result.index}]: ${decoratedFolderOptions[result.index].text}`)
394+
logDebug('userInput / chooseFolder CHOSEN FOLDER', `- simpleFolderOptions[${result.index}]: ${simpleFolderOptions[result.index].label}`)
395+
logDebug('userInput / chooseFolder CHOSEN FOLDER', `- folders[${result.index}]: ${folders[result.index]} (using result index)`)
396+
logDebug('userInput / chooseFolder CHOSEN FOLDER', `- actualIndex: ${actualIndex} folders[${actualIndex}]: ${folders[actualIndex]} (using actualIndex)`)
388397
}
389398

390399
// Handle new folder creation, if requested
@@ -402,7 +411,23 @@ export async function chooseFolder(
402411
// not including add new folder option
403412
result = await chooseDecoratedOptionWithModifiers(msg, decoratedFolderOptions)
404413
clo(result, 'chooseFolder chooseDecoratedOptionWithModifiers result') // ✅
405-
value = folders[result.index]
414+
actualIndex = result.index
415+
value = folders[actualIndex]
416+
}
417+
logDebug(
418+
'userInput / chooseFolder',
419+
`User chose: result.index:${result.index} ${
420+
includeNewFolderOption ? `(actualIndex in folders array: ${actualIndex} because running with includeNewFolderOption),` : ''
421+
} optClickedOnFolder: ${String(optClickedOnFolder)} / folders: ${folders.length}`,
422+
)
423+
// logDebug output a map of the folders arrray with 3 items on either side of the chosen index
424+
// realizing that folder index could be 0, so we need to handle that
425+
if (actualIndex > -1) {
426+
const foldersSample = folders.map((f, i) => ({ ...(typeof f === 'string' ? { label: f, value: f } : f), index: i })).slice(Math.max(0, actualIndex - 3), actualIndex + 3)
427+
logDebug('userInput / chooseFolder', `foldersSample +/- 3 of chosen index`)
428+
foldersSample.forEach((folder) => {
429+
logDebug('userInput / chooseFolder', ` [${folder.index}]: ${folder.label}${folder.index === actualIndex ? ' <== (chosen)' : ''}`)
430+
})
406431
}
407432
} else {
408433
// ✅ for both private + teamspace
@@ -463,7 +488,7 @@ export async function chooseFolder(
463488
folder = '/'
464489
}
465490

466-
logDebug(`userInput / chooseFolder`, ` -> "${folder}"`)
491+
logDebug(`userInput / chooseFolder`, ` -> returning "${folder}"`)
467492
return folder
468493
} catch (error) {
469494
logError('userInput / chooseFolder', error.message)

0 commit comments

Comments
 (0)