importNotion.ts
TLDR
The importNotion.ts file is responsible for importing data from a CSV file in Notion and converting it into a board archive. It reads the CSV file, converts the data into board and block objects, and then saves the output as a board archive file.
Methods
main
This method is the entry point of the script. It reads command-line arguments, validates input folder and CSV file, reads the CSV file, converts the data, and saves the output as a board archive file.
getCsvFilePath
This method takes an input folder path and returns the path of the CSV file in the folder, if it exists.
getMarkdown
This method takes a card title and returns the markdown content associated with the card, if it exists in the markdown folder.
getColumns
This method takes an array of input data and returns an array of column names extracted from the first row of the data.
convert
This method takes input data and a board title, and converts the data into board and block objects. It creates a board, sets card properties based on columns, creates a board view, creates cards with properties and notes from markdown, and returns the created boards and blocks.
showHelp
This method displays the help text for the script.
Classes
None
import csv from 'csvtojson'
import * as fs from 'fs'
import minimist from 'minimist'
import path from 'path'
import {exit} from 'process'
import {ArchiveUtils} from '../util/archive'
import {Block} from '../../webapp/src/blocks/block'
import {Board} from '../../webapp/src/blocks/board'
import {IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'
import {createBoardView} from '../../webapp/src/blocks/boardView'
import {createCard} from '../../webapp/src/blocks/card'
import {createTextBlock} from '../../webapp/src/blocks/textBlock'
import {Utils} from './utils'
// HACKHACK: To allow Utils.CreateGuid to work
(global.window as any) = {}
let markdownFolder: string
const optionColors = [
// 'propColorDefault',
'propColorGray',
'propColorBrown',
'propColorOrange',
'propColorYellow',
'propColorGreen',
'propColorBlue',
'propColorPurple',
'propColorPink',
'propColorRed',
]
let optionColorIndex = 0
async function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
const inputFolder = args['i']
const outputFile = args['o'] || 'archive.boardarchive'
if (!inputFolder) {
showHelp()
}
if (!fs.existsSync(inputFolder)){
console.log(`Folder not found: ${inputFolder}`)
exit(2)
}
const inputFile = getCsvFilePath(inputFolder)
if (!inputFile) {
console.log(`.csv file not found in folder: ${inputFolder}`)
exit(2)
}
console.log(`inputFile: ${inputFile}`)
// Read input
const input = await csv().fromFile(inputFile)
console.log(`Read ${input.length} rows.`)
console.log(input)
const basename = path.basename(inputFile, '.csv')
const components = basename.split(' ')
components.pop()
const title = components.join(' ')
console.log(`title: ${title}`)
markdownFolder = path.join(inputFolder, basename)
// Convert
const [boards, blocks] = convert(input, title)
// Save output
// TODO: Stream output
const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)
fs.writeFileSync(outputFile, outputData)
console.log(`Exported to ${outputFile}`)
}
function getCsvFilePath(inputFolder: string): string | undefined {
const files = fs.readdirSync(inputFolder)
const file = files.find(o => path.extname(o).toLowerCase() === '.csv')
return file ? path.join(inputFolder, file) : undefined
}
function getMarkdown(cardTitle: string): string | undefined {
if (!fs.existsSync(markdownFolder)){ return undefined}
const files = fs.readdirSync(markdownFolder)
const file = files.find((o) => {
const basename = path.basename(o)
const components = basename.split(' ')
const fileCardTitle = components.slice(0, components.length-1).join(' ')
if (fileCardTitle === cardTitle) {
return o
}
})
if (file) {
const filePath = path.join(markdownFolder, file)
const markdown = fs.readFileSync(filePath, 'utf-8')
// TODO: Remove header from markdown, which repets card title and properties
return markdown
}
return undefined
}
function getColumns(input: any[]) {
const row = input[0]
const keys = Object.keys(row)
// The first key (column) is the card title
return keys.slice(1)
}
function convert(input: any[], title: string): [Board[], Block[]] {
const boards: Board[] = []
const blocks: Block[] = []
// Board
const board = createBoard()
console.log(`Board: ${title}`)
board.title = title
// Each column is a card property
const columns = getColumns(input)
columns.forEach(column => {
const cardProperty: IPropertyTemplate = {
id: Utils.createGuid(),
name: column,
type: 'select',
options: []
}
board.cardProperties.push(cardProperty)
})
// Set all column types to select
// TODO: Detect column type
boards.push(board)
// Board view
const view = createBoardView()
view.title = 'Board View'
view.fields.viewType = 'board'
view.boardId = board.id
view.parentId = board.id
blocks.push(view)
// Cards
input.forEach(row => {
const keys = Object.keys(row)
console.log(keys)
if (keys.length < 1) {
console.error(`Expected at least one column`)
return blocks
}
const titleKey = keys[0]
const title = row[titleKey]
console.log(`Card: ${title}`)
const outCard = createCard()
outCard.title = title
outCard.boardId = board.id
outCard.parentId = board.id
// Card properties, skip first key which is the title
for (const key of keys.slice(1)) {
const value = row[key]
if (!value) {
// Skip empty values
continue
}
const cardProperty = board.cardProperties.find((o) => o.name === key)!
let option = cardProperty.options.find((o) => o.value === value)
if (!option) {
const color = optionColors[optionColorIndex % optionColors.length]
optionColorIndex += 1
option = {
id: Utils.createGuid(),
value,
color: color,
}
cardProperty.options.push(option)
}
outCard.fields.properties[cardProperty.id] = option.id
}
blocks.push(outCard)
// Card notes from markdown
const markdown = getMarkdown(title)
if (markdown) {
console.log(`Markdown: ${markdown.length} bytes`)
const text = createTextBlock()
text.title = markdown
text.boardId = board.id
text.parentId = outCard.id
blocks.push(text)
outCard.fields.contentOrder = [text.id]
}
})
console.log('')
console.log(`Found ${input.length} card(s).`)
return [boards, blocks]
}
function showHelp() {
console.log('import -i <input.json> -o [output.boardarchive]')
exit(1)
}
main()