Skip to content

Google Keep: Add created/modified timestamps & handle duplicates#472

Open
keanucz wants to merge 1 commit intoobsidianmd:masterfrom
keanucz:fix/keep-time-import-bugs
Open

Google Keep: Add created/modified timestamps & handle duplicates#472
keanucz wants to merge 1 commit intoobsidianmd:masterfrom
keanucz:fix/keep-time-import-bugs

Conversation

@keanucz
Copy link
Copy Markdown

@keanucz keanucz commented Dec 24, 2025

This fixes issues #377 and #385. I've tested it against my local vault (by importing my own Keep takeout with a dozen or so attachments and 675 notes) and this PR adds the following functionality:

  1. Adds keepCreatedAt and keepUpdatedAt to the frontmatter of all imported notes, output using ISO8601.
  2. Adds a dropdown for how duplicate notes should be handled (e.g. if the user reimports the same takeout file but with substantial changes months later for example) - the default behaviour is to skip any already imported duplicate notes.
2025-12-24_16-40 2025-12-24_16-41

I've never contributed to an Obsidian plugin before so I'm happy to take any pointers or suggestions for how my implementation could be refined!

@keanucz keanucz changed the title Google Keep: Add created/modified timestamps to front matter and hand… Google Keep: Add created/modified timestamps & handle duplicates Dec 24, 2025
Copy link
Copy Markdown
Contributor

@tgrosinger tgrosinger left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @keanucz for helping to improve the Keep importer! I've left some comments and questions. Let me know what you think.

// may not find an existing folder if the casing doesn't match, and then
// `createFolder` throws because it already exists. Try again with an
// insensitive lookup before failing.
folder = vault.getAbstractFileByPathInsensitive(normalizedFolderPath);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change made above on 155 seems like it should cover this case and remove the need to repeat the lookup here.

The only case I can think of where this would have an effect is if you are on a case-sensitive OS and the provided path is a file, but an insensitive match finds a folder. I might argue in that case we should just be throwing an error, not finding the insensitive-matching folder on a case-sensitive OS.

await importer.import(ctx);
}
catch (e) {
ctx.reportFailed('Import', e);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were you running into an importer that was throwing errors? Maybe we should fix that more directly?

existingFileBehavior: ExistingFileBehavior = 'skip';

init() {
this.importArchived = this.importArchived ?? false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of these lines?


this.existingFileSetting = new Setting(this.modal.contentEl)
.setName('If note or attachment already exists')
.setDesc('When importing into the same folder again, choose to skip, overwrite, or create a duplicate.')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.setDesc('When importing into the same folder again, choose to skip, overwrite, or create a duplicate.')
.setDesc('When importing into the same folder again, choose how to handle content that was previously imported.')


const createdMs = normalizeEpochMs(keepJson.createdTimestampUsec);
const editedMs = normalizeEpochMs(keepJson.userEditedTimestampUsec);
if (!createdMs && !editedMs) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be ||?

ctx.reportAttachmentSuccess(fullpath);
const imported = await this.copyFile(file, assetFolderPath, ctx);
if (imported) {
ctx.reportAttachmentSuccess(fullpath);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this should be moved into copyFile instead? Then it would be next to the other reporting calls in that function.

ctx.reportNoteSuccess(fullpath);
const imported = await this.convertKeepJson(keepJson, folder, basename, fullpath, ctx);
if (imported) {
ctx.reportNoteSuccess(fullpath);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as above. Move this into convertKeepJson?

const createdMs = normalizeEpochMs(keepJson.createdTimestampUsec);
const editedMs = normalizeEpochMs(keepJson.userEditedTimestampUsec) ?? createdMs;

if (createdMs !== undefined) frontMatter['keepCreatedAt'] = new Date(createdMs).toISOString();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've actually been having a very similar conversation about this in !467.

I don't think we need to store these times in the frontmatter. They should be set on the file correctly when creating the file. Setting it on the file means that in a Base you can view the file.mtime and file.ctime, filter using them, or even transform it into another format.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants