Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
.DS_Store
coverage
*.log
.env
.env
36 changes: 26 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,36 @@ Run:

## CLI Options

* `-p` or `--path` - Path to the Google Keep folder
* `-o` or `--output` - Path to the output folder (will be created if it doesn't exist and must be different from the input folder)
* `-a` or `--archive` - Whether to include archived notes. Defaults to `false`.
* `-m` or `--mode` - Mode for conversion. Can be `pages` or `mixed`. Defaults to `mixed`. `mixed` mode will convert Keep notes with titles to Anytype `page` and Keep notes without titles to Anytype `note`. `pages` mode will convert all Keep notes to Anytype `page`, and will use the created date as the title if the Keep note does not have a title.
- `-p` or `--path` - Path to the Google Keep folder
- `-o` or `--output` - Path to the output folder (will be created if it doesn't exist and must be different from the input folder)
- `-a` or `--archive` - Whether to include archived notes. Defaults to `false`.
- `-t` or `--trash` - Whether to include trashed notes. Defaults to `false`.
- `-m` or `--mode` - Mode for conversion. Can be `pages` or `mixed`. Defaults to `mixed`. `mixed` mode will convert Keep notes with titles to Anytype `page` and Keep notes without titles to Anytype `note`. `pages` mode will convert all Keep notes to Anytype `page`, and will use the created date as the title if the Keep note does not have a title.
- `-e` or `--emoji` - The emoji to use for the Anytype object. Defaults to nothing.
- `-d` or `--metadata` - Whether to include additional metadata from Google Keep. Defaults to `false`. Refer to Notes for more information.
- `-r` or `--relation` - Include the description relation as a block in the Anytype object. Defaults to `false`.

## Import

In anytype, select `file -> import` then `Any-Block` and select the output folder to bulk import.

## Notes

* Does not import Google Keep tags
* Does not import Google Keep images
* Does not import Google Keep note colors
* Modifies the created and modified dates to match the Google Keep note
* If the Keep note does not have a title, it uses the created date as the title
* Automatically parses any hyperlinks or annotations
- You should try importing into a blank space first to see if the conversion works as expected.
- Does not import Google Keep images
- Modifies the created and modified dates to match the Google Keep note
- If the Keep note does not have a title, it uses the created date as the title
- Automatically parses any hyperlinks or annotations
- Metadata will be included in the description if the `metadata` flag is set to `true`. This metadata includes:
- `Color` - The colour of the note if it's not DEFAULT
- `IsPinned` - Whether the note is pinned
- `IsArchived` - Whether the note is archived
- `IsTrashed` - Whether the note is in the trash
- `Labels` - The labels attached to the note

## Example

`yarn cli -p <path-to-keep-folder>/Takeout/Keep -o <output-folder> -a -t -d -r -m pages -e 🗒️`

In this example, the command above was run. This includes all archived notes and adds the labels and colours of the notes in the Description relation. The creation dates also match the original Google Keep notes.
![image](img/example-import.png)
Binary file added img/example-import.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module.exports = {
roots: ['<rootDir>/src'],
roots: ["<rootDir>/src"],
testMatch: [
"**/__tests__/**/*.+(ts|tsx|js)",
"**/?(*.)+(spec|test).+(ts|tsx|js)"
"**/?(*.)+(spec|test).+(ts|tsx|js)",
],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest"
"^.+\\.(ts|tsx)$": "ts-jest",
},
}
};
126 changes: 69 additions & 57 deletions src/anyBlock/blocks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { v4 as uuidv4 } from 'uuid'
import { v4 as uuidv4 } from "uuid";
import {
AnnotationConfig,
Block,
Expand All @@ -13,124 +13,135 @@ import {
Mark,
Marks,
Range,
RelationConfig,
Restrictions,
} from './types'
import { extractUrls } from '../utils'
} from "./types";
import { extractUrls } from "../utils";

const createRestrictions = (
edit = false,
remove = true,
drag = true,
dropOn = true
): { restrictions: Restrictions } => {
const restrictions: Restrictions = {}
const restrictions: Restrictions = {};

if (edit) restrictions.edit = edit
if (remove) restrictions.remove = remove
if (drag) restrictions.drag = drag
if (dropOn) restrictions.dropOn = dropOn
if (edit) restrictions.edit = edit;
if (remove) restrictions.remove = remove;
if (drag) restrictions.drag = drag;
if (dropOn) restrictions.dropOn = dropOn;

return { restrictions }
}
return { restrictions };
};

const generateLinkMarksForText = (
text: string
): Marks | Record<string, never> => {
const marks: Mark[] = []
const urls = extractUrls(text)
const marks: Mark[] = [];
const urls = extractUrls(text);
if (urls.length === 0) {
return {}
return {};
}

for (const url of urls) {
const range: Range =
url.start > 0 ? { to: url.end, from: url.start } : { to: url.end }
url.start > 0 ? { to: url.end, from: url.start } : { to: url.end };
marks.push({
range,
type: 'Link',
type: "Link",
param: url.url,
})
});
}

return { marks }
}
return { marks };
};

const generateTextContent = (config: ContentfulConfig) => {
const marks: Marks = generateLinkMarksForText(config.content)
const marks: Marks = generateLinkMarksForText(config.content);
return {
text: config.content,
marks,
}
}
};
};

// Block Handlers
const headerBlockHandler: BlockHandler<HeaderConfig> = {
prepareContent(config) {
const childrenIds = ['featuredRelations']
if (config.objectType === 'page') {
childrenIds.push('title', 'description')
const childrenIds = ["featuredRelations"];
if (config.objectType === "page") {
childrenIds.push("title", "description");
}

return {
id: 'header',
id: "header",
...createRestrictions(true),
layout: { style: 'Header' },
layout: { style: "Header" },
childrenIds,
}
};
},
}
};

const featuredRelationsBlockHandler: BlockHandler<never> = {
prepareContent() {
return {
id: 'featuredRelations',
id: "featuredRelations",
...createRestrictions(),
featuredRelations: {},
}
};
},
}
};

const descriptionBlockHandler: BlockHandler<never> = {
prepareContent() {
return {
id: 'description',
id: "description",
...createRestrictions(),
fields: { _detailsKey: 'description' },
text: { style: 'Description', marks: {} },
}
fields: { _detailsKey: "description" },
text: { style: "Description", marks: {} },
};
},
}
};

const titleBlockHandler: BlockHandler<never> = {
prepareContent() {
return {
id: 'title',
id: "title",
...createRestrictions(),
fields: { _detailsKey: ['name', 'done'] },
text: { style: 'Title', marks: {} },
}
fields: { _detailsKey: ["name", "done"] },
text: { style: "Title", marks: {} },
};
},
}
};

const relationBlockHandler: BlockHandler<RelationConfig> = {
prepareContent(config) {
return {
relation: {
key: config.relationKey,
},
};
},
};

const textBlockHandler: BlockHandler<ContentfulConfig> = {
prepareContent(config) {
return {
text: generateTextContent(config),
}
};
},
}
};

const listBlockHandler: BlockHandler<ListConfig> = {
prepareContent(config) {
return {
text: {
...generateTextContent(config),
style: 'Checkbox',
style: "Checkbox",
checked: config.checked,
},
}
};
},
}
};

const annotationBlockHandler: BlockHandler<AnnotationConfig> = {
prepareContent(config) {
Expand All @@ -141,15 +152,15 @@ const annotationBlockHandler: BlockHandler<AnnotationConfig> = {
marks: [
{
range: { to: config.content.length },
type: 'Link',
type: "Link",
param: config.url,
},
],
},
},
}
};
},
}
};

const handlers: HandlersMap = {
Text: textBlockHandler,
Expand All @@ -159,21 +170,22 @@ const handlers: HandlersMap = {
FeaturedRelations: featuredRelationsBlockHandler,
Description: descriptionBlockHandler,
Title: titleBlockHandler,
}
Relation: relationBlockHandler,
};

export const createBlock = <T extends BlockType>(
type: T,
config: HandlerConfig[T]
): BlockWithId => {
let id = uuidv4()
const handler = handlers[type]
let id = uuidv4();
const handler = handlers[type];

const fields = handler.prepareContent(config as HandlerConfig[T])
const fields = handler.prepareContent(config as HandlerConfig[T]);

if (fields.id) {
id = fields.id
id = fields.id;
}

const block: Block = { id, ...fields }
return { block, id }
}
const block: Block = { id, ...fields };
return { block, id };
};
Loading