focalboard/import/todoist/importTodoist.ts
2021-04-22 15:55:46 -07:00

195 lines
5.4 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import * as fs from 'fs'
import minimist from 'minimist'
import {exit} from 'process'
import {ArchiveUtils} from '../../webapp/src/blocks/archive'
import {IBlock} from '../../webapp/src/blocks/block'
import {IPropertyOption, IPropertyTemplate, MutableBoard} from '../../webapp/src/blocks/board'
import {MutableBoardView} from '../../webapp/src/blocks/boardView'
import {MutableCard} from '../../webapp/src/blocks/card'
import {MutableTextBlock} from '../../webapp/src/blocks/textBlock'
import {Item, Project, Section, Todoist} from './todoist'
import {Utils} from './utils'
// HACKHACK: To allow Utils.CreateGuid to work
(global.window as any) = {}
const optionColors = [
// 'propColorDefault',
'propColorGray',
'propColorBrown',
'propColorOrange',
'propColorYellow',
'propColorGreen',
'propColorBlue',
'propColorPurple',
'propColorPink',
'propColorRed',
]
const defaultSections = ['No Status', 'Next Up', 'In Progress', 'Completed', 'Archived'].map(title => {
return {
id: Utils.createGuid(),
name: title,
} as Section
})
let noStatusSectionID: any
let optionColorIndex = 0
function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
const inputFile = args['i']
const outputFile = args['o'] || 'archive.focalboard'
if (!inputFile) {
showHelp()
}
if (!fs.existsSync(inputFile)) {
console.error(`File not found: ${inputFile}`)
exit(2)
}
// Read input
const inputData = fs.readFileSync(inputFile, 'utf-8')
const input = JSON.parse(inputData) as Todoist
const blocks = [] as IBlock[]
input.projects.forEach(project => {
blocks.push(...convert(input, project))
})
// Save output
// TODO: Stream output
const outputData = ArchiveUtils.buildBlockArchive(blocks)
fs.writeFileSync(outputFile, outputData)
console.log(`Exported to ${outputFile}`)
}
function convert(input: Todoist, project: Project): IBlock[] {
const blocks: IBlock[] = []
if (project.name === 'Inbox') {
return blocks
}
// Board
const board = new MutableBoard()
console.log(`Board: ${project.name}`)
board.rootId = board.id
board.title = project.name
board.description = project.name
// Convert lists (columns) to a Select property
const optionIdMap = new Map<string, string>()
const options: IPropertyOption[] = []
const columns = getProjectColumns(input, project)
console.log("columns: " + JSON.stringify(columns))
columns.forEach(list => {
const optionId = Utils.createGuid()
if (list.name === 'No Status') {
noStatusSectionID = list.id
}
optionIdMap.set(String(list.id), optionId)
const color = optionColors[optionColorIndex % optionColors.length]
optionColorIndex += 1
const option: IPropertyOption = {
id: optionId,
value: list.name,
color,
}
options.push(option)
})
const cardProperty: IPropertyTemplate = {
id: Utils.createGuid(),
name: 'List',
type: 'select',
options
}
board.cardProperties = [cardProperty]
blocks.push(board)
// Board view
const view = new MutableBoardView()
view.title = 'Board View'
view.viewType = 'board'
view.rootId = board.id
view.parentId = board.id
blocks.push(view)
// Cards
const cards = getProjectCards(input, project)
cards.forEach(card => {
const outCard = new MutableCard()
outCard.title = card.content
outCard.rootId = board.id
outCard.parentId = board.id
// Map lists to Select property options
const cardSectionId = card.section_id ? card.section_id : noStatusSectionID
const optionId = optionIdMap.get(String(cardSectionId))
if (optionId) {
outCard.properties[cardProperty.id] = optionId
} else {
console.warn(`Invalid idList: ${cardSectionId} for card: ${card.content}`)
}
blocks.push(outCard)
// console.log(`\t${card.desc}`)
const text = new MutableTextBlock()
text.title = getCardDescription(input, card).join('\n\n')
text.rootId = board.id
text.parentId = outCard.id
blocks.push(text)
outCard.contentOrder = [text.id]
})
return blocks
}
function getProjectColumns(input: Todoist, project: Project): Array<Section> {
const sections = [{
id: noStatusSectionID,
name: 'No Section',
} as Section]
sections.push(...input.sections.filter(section => section.project_id === project.id))
return sections.length > 1 ? sections : defaultSections
}
function getProjectCards(input: Todoist, project: Project): Array<Item> {
return input.items.filter(item => item.project_id === project.id)
}
function getCardDescription(input: Todoist, item: Item): Array<string> {
return input.notes.filter(note => note.item_id === item.id).map(item => {
let description = ""
if (item.content) {
description = item.content
}
if (item.file_attachment) {
description += `\n\nAttachment: [${item.file_attachment.title}](${item.file_attachment.url})`
}
return description
})
}
function showHelp() {
exit(1)
}
main()