199 lines
5.5 KiB
TypeScript
199 lines
5.5 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 '../util/archive'
|
|
import {Block} from '../../webapp/src/blocks/block'
|
|
import {Board} from '../../webapp/src/blocks/board'
|
|
import {IPropertyOption, 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 {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 boards = [] as Board[]
|
|
const blocks = [] as Block[]
|
|
|
|
input.projects.forEach(project => {
|
|
const [brds, blks] = convert(input, project)
|
|
boards.push(...brds)
|
|
blocks.push(...blks)
|
|
})
|
|
|
|
// Save output
|
|
// TODO: Stream output
|
|
const outputData = ArchiveUtils.buildBlockArchive(boards, blocks)
|
|
fs.writeFileSync(outputFile, outputData)
|
|
|
|
console.log(`Exported to ${outputFile}`)
|
|
}
|
|
|
|
function convert(input: Todoist, project: Project): [Board[], Block[]] {
|
|
const boards: Board[] = []
|
|
const blocks: Block[] = []
|
|
|
|
if (project.name === 'Inbox') {
|
|
return [boards, blocks]
|
|
}
|
|
|
|
// Board
|
|
const board = createBoard()
|
|
console.log(`Board: ${project.name}`)
|
|
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]
|
|
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
|
|
const cards = getProjectCards(input, project)
|
|
cards.forEach(card => {
|
|
const outCard = createCard()
|
|
outCard.title = card.content
|
|
outCard.boardId = 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.fields.properties[cardProperty.id] = optionId
|
|
} else {
|
|
console.warn(`Invalid idList: ${cardSectionId} for card: ${card.content}`)
|
|
}
|
|
|
|
blocks.push(outCard)
|
|
|
|
// console.log(`\t${card.desc}`)
|
|
const text = createTextBlock()
|
|
text.title = getCardDescription(input, card).join('\n\n')
|
|
text.boardId = board.id
|
|
text.parentId = outCard.id
|
|
blocks.push(text)
|
|
|
|
outCard.fields.contentOrder = [text.id]
|
|
})
|
|
|
|
return [boards, 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()
|