diff --git a/docs/features/daily-notes.md b/docs/features/daily-notes.md index 555ce78b..033b8fdb 100644 --- a/docs/features/daily-notes.md +++ b/docs/features/daily-notes.md @@ -14,8 +14,8 @@ By default, Daily Notes will be created in a file called `yyyy-mm-dd.md` in the These settings can be overridden in your workspace or global `.vscode/settings.json` file, using the [**dateformat** date masking syntax](https://github.com/felixge/node-dateformat#mask-options): -```json - "foam.openDailyNote.directory": "journal", +```jsonc + "foam.openDailyNote.directory": "journal", // a relative directory path will get appended to the workspace root. An absolute directory path will be used unmodified. "foam.openDailyNote.filenameFormat": "'daily-note'-yyyy-mm-dd", "foam.openDailyNote.fileExtension": "mdx", "foam.openDailyNote.titleFormat": "'Journal Entry, ' dddd, mmmm d", diff --git a/packages/foam-core/src/common/uri.ts b/packages/foam-core/src/common/uri.ts index de6b836b..27e7d4bb 100644 --- a/packages/foam-core/src/common/uri.ts +++ b/packages/foam-core/src/common/uri.ts @@ -452,7 +452,7 @@ interface UriState extends UriComponents { const _pathSepMarker = isWindows ? 1 : undefined; -// This class exists so that URI is compatibile with vscode.Uri (API). +// This class exists so that URI is compatible with vscode.Uri (API). class Uri extends URI { _formatted: string | null = null; _fsPath: string | null = null; diff --git a/packages/foam-vscode/src/dated-notes.test.ts b/packages/foam-vscode/src/dated-notes.test.ts new file mode 100644 index 00000000..cb979ec7 --- /dev/null +++ b/packages/foam-vscode/src/dated-notes.test.ts @@ -0,0 +1,74 @@ +import { workspace } from 'vscode'; +import { getDailyNotePath } from './dated-notes'; + +describe('getDailyNotePath', () => { + test('Adds the root directory to relative directories (Posix paths)', async () => { + const date = new Date('2021-02-07T00:00:00Z'); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const isoDate = `${year}-0${month}-0${day}`; + + await workspace + .getConfiguration('foam') + .update('openDailyNote.directory', 'journal/subdir'); + const foamConfiguration = workspace.getConfiguration('foam'); + expect(getDailyNotePath(foamConfiguration, date).path).toMatch( + new RegExp(`journal[\\\\/]subdir[\\\\/]${isoDate}.md$`) + ); + }); + + test('Uses absolute directories without modification (Posix paths)', async () => { + const date = new Date('2021-02-07T00:00:00Z'); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const isoDate = `${year}-0${month}-0${day}`; + + await workspace + .getConfiguration('foam') + .update('openDailyNote.directory', '/absolute_path/journal'); + const foamConfiguration = workspace.getConfiguration('foam'); + expect(getDailyNotePath(foamConfiguration, date).path).toMatch( + new RegExp(`^/absolute_path/journal/${isoDate}.md`) + ); + }); + + test('Adds the root directory to relative directories (Windows paths)', async () => { + const date = new Date('2021-02-07T00:00:00Z'); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const isoDate = `${year}-0${month}-0${day}`; + + await workspace + .getConfiguration('foam') + .update('openDailyNote.directory', 'journal\\subdir'); + const foamConfiguration = workspace.getConfiguration('foam'); + expect(getDailyNotePath(foamConfiguration, date).path).toMatch( + new RegExp(`journal[\\\\/]subdir[\\\\/]${isoDate}.md$`) + ); + }); + + test('Uses absolute directories without modification (Windows paths)', async () => { + // While technically the test passes on all OS's, it's only because the test is overly loose. + // On Posix systems, this test actually does modify the path, since Windows style paths are + // considered to be relative paths. So while this test passes on Posix systems, it is not + // because it treats it as an absolute path, but rather that the test doesn't check the same thing. + // This was considered "good enough" instead of introducing a dependency like `skip-if` to skip the + // test on Posix systems. + const date = new Date('2021-02-07T00:00:00Z'); + const year = date.getFullYear(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const isoDate = `${year}-0${month}-0${day}`; + + await workspace + .getConfiguration('foam') + .update('openDailyNote.directory', 'C:\\absolute_path\\journal'); + const foamConfiguration = workspace.getConfiguration('foam'); + expect(getDailyNotePath(foamConfiguration, date).path).toMatch( + new RegExp(`/C:[\\\\/]absolute_path[\\\\/]journal[\\\\/]${isoDate}.md`) + ); + }); +}); diff --git a/packages/foam-vscode/src/dated-notes.ts b/packages/foam-vscode/src/dated-notes.ts index 2d4f4d88..60ab6d7b 100644 --- a/packages/foam-vscode/src/dated-notes.ts +++ b/packages/foam-vscode/src/dated-notes.ts @@ -1,8 +1,9 @@ import { workspace, WorkspaceConfiguration } from 'vscode'; -import { dirname, join } from 'path'; import dateFormat from 'dateformat'; import * as fs from 'fs'; -import { docConfig, focusNote, pathExists } from './utils'; +import { isAbsolute } from 'path'; +import { docConfig, focusNote, getDirname, pathExists } from './utils'; +import { URI } from 'foam-core'; async function openDailyNoteFor(date?: Date) { const foamConfiguration = workspace.getConfiguration('foam'); @@ -17,13 +18,24 @@ async function openDailyNoteFor(date?: Date) { ); await focusNote(dailyNotePath, isNew); } -function getDailyNotePath(configuration: WorkspaceConfiguration, date: Date) { - const rootDirectory = workspace.workspaceFolders[0].uri.fsPath; + +function getDailyNotePath( + configuration: WorkspaceConfiguration, + date: Date +): URI { const dailyNoteDirectory: string = configuration.get('openDailyNote.directory') ?? '.'; const dailyNoteFilename = getDailyNoteFileName(configuration, date); - return join(rootDirectory, dailyNoteDirectory, dailyNoteFilename); + if (isAbsolute(dailyNoteDirectory)) { + return URI.joinPath(URI.file(dailyNoteDirectory), dailyNoteFilename); + } else { + return URI.joinPath( + workspace.workspaceFolders[0].uri, + dailyNoteDirectory, + dailyNoteFilename + ); + } } function getDailyNoteFileName( @@ -42,7 +54,7 @@ function getDailyNoteFileName( async function createDailyNoteIfNotExists( configuration: WorkspaceConfiguration, - dailyNotePath: string, + dailyNotePath: URI, currentDate: Date ) { if (await pathExists(dailyNotePath)) { @@ -56,7 +68,7 @@ async function createDailyNoteIfNotExists( configuration.get('openDailyNote.filenameFormat'); await fs.promises.writeFile( - dailyNotePath, + dailyNotePath.fsPath, `# ${dateFormat(currentDate, titleFormat, false)}${docConfig.eol}${ docConfig.eol }` @@ -65,11 +77,11 @@ async function createDailyNoteIfNotExists( return true; } -async function createDailyNoteDirectoryIfNotExists(dailyNotePath: string) { - const dailyNoteDirectory = dirname(dailyNotePath); +async function createDailyNoteDirectoryIfNotExists(dailyNotePath: URI) { + const dailyNoteDirectory = getDirname(dailyNotePath); if (!(await pathExists(dailyNoteDirectory))) { - await fs.promises.mkdir(dailyNoteDirectory, { recursive: true }); + await fs.promises.mkdir(dailyNoteDirectory.fsPath, { recursive: true }); } } diff --git a/packages/foam-vscode/src/features/create-from-template.ts b/packages/foam-vscode/src/features/create-from-template.ts index facaceef..985d53cc 100644 --- a/packages/foam-vscode/src/features/create-from-template.ts +++ b/packages/foam-vscode/src/features/create-from-template.ts @@ -91,11 +91,9 @@ async function createNoteFromTemplate(): Promise { URI.joinPath(templatesDir, selectedTemplate) ); const snippet = new SnippetString(templateText.toString()); - await workspace.fs.writeFile( - URI.file(filename), - new TextEncoder().encode('') - ); - await focusNote(filename, true); + const filenameURI = URI.file(filename); + await workspace.fs.writeFile(filenameURI, new TextEncoder().encode('')); + await focusNote(filenameURI, true); await window.activeTextEditor.insertSnippet(snippet); } @@ -125,11 +123,12 @@ async function createNewTemplate(): Promise { return; } + const filenameURI = URI.file(filename); await workspace.fs.writeFile( - URI.file(filename), + filenameURI, new TextEncoder().encode(templateContent) ); - await focusNote(filename, false); + await focusNote(filenameURI, false); } const feature: FoamFeature = { diff --git a/packages/foam-vscode/src/features/open-random-note.ts b/packages/foam-vscode/src/features/open-random-note.ts index 271074fe..d87b4435 100644 --- a/packages/foam-vscode/src/features/open-random-note.ts +++ b/packages/foam-vscode/src/features/open-random-note.ts @@ -22,7 +22,7 @@ const feature: FoamFeature = { randomNoteIndex = (randomNoteIndex + 1) % notes.length; } - focusNote(notes[randomNoteIndex].uri.path, false); + focusNote(notes[randomNoteIndex].uri, false); }) ); }, diff --git a/packages/foam-vscode/src/test/suite.ts b/packages/foam-vscode/src/test/suite.ts index cf2d2322..c09b6989 100644 --- a/packages/foam-vscode/src/test/suite.ts +++ b/packages/foam-vscode/src/test/suite.ts @@ -3,7 +3,7 @@ * We use the following convention in Foam: * - *.test.ts are unit tests * they might still rely on vscode API and hence will be run in this environment, but - * are fundamentally about testing functions in isolations + * are fundamentally about testing functions in isolation * - *.spec.ts are integration tests * they will make direct use of the vscode API to be invoked as commands, create editors, * and so on.. diff --git a/packages/foam-vscode/src/utils.ts b/packages/foam-vscode/src/utils.ts index 0b33dcff..69838f15 100644 --- a/packages/foam-vscode/src/utils.ts +++ b/packages/foam-vscode/src/utils.ts @@ -6,7 +6,6 @@ import { Position, TextEditor, workspace, - Uri, Selection, MarkdownString, version, @@ -15,6 +14,8 @@ import * as fs from 'fs'; import { Logger, Resource, Note } from 'foam-core'; import matter from 'gray-matter'; import removeMarkdown from 'remove-markdown'; +import { URI } from 'foam-core'; +import { posix } from 'path'; interface Point { line: number; @@ -144,14 +145,23 @@ export function toTitleCase(word: string): string { .join(' '); } +/** + * Get a URI that represents the dirname of a URI + * + * @param uri The URI to get the dirname from + */ +export function getDirname(uri: URI): URI { + return URI.file(posix.parse(uri.path).dir); +} + /** * Verify the given path exists in the file system * * @param path The path to verify */ -export function pathExists(path: string) { +export function pathExists(path: URI) { return fs.promises - .access(path, fs.constants.F_OK) + .access(path.fsPath, fs.constants.F_OK) .then(() => true) .catch(() => false); } @@ -179,8 +189,8 @@ export function isNone( return value == null; // eslint-disable-line } -export async function focusNote(notePath: string, moveCursorToEnd: boolean) { - const document = await workspace.openTextDocument(Uri.file(notePath)); +export async function focusNote(notePath: URI, moveCursorToEnd: boolean) { + const document = await workspace.openTextDocument(notePath); const editor = await window.showTextDocument(document); // Move the cursor to end of the file