import Nast from 'notajs-types/nast' import Notion from 'notajs-types/notion' import { COLLECTION_VIEW_TYPES, COLLECTION_ITEM_PROPERTY_TYPES, CSS } from '../constants' import { raiseWarning } from '../log-utils' import { escapeString, renderTitle } from '../render-utils' function renderCollection( node: Nast.Collection ): string { let viewMeta = node.views.find(view => view.id === node.defaultViewId) if (typeof viewMeta === 'undefined') { raiseWarning(`Cannot find view of ${node.defaultViewId}`) return '' } let pages = node.blocks let sortQueries = viewMeta.query.sort if (sortQueries != null) { let sortFunc = factorySortFunc(sortQueries) pages = pages.sort(sortFunc) } switch (viewMeta.type) { case COLLECTION_VIEW_TYPES.table: return renderTable(node, pages, viewMeta.format) case COLLECTION_VIEW_TYPES.gallery: return renderGallery(node, pages, viewMeta.format) default: raiseWarning(`No render function for collection view "${viewMeta.type}".`) return '' } } function renderTable( node: Nast.Collection, pages: Nast.Page[], viewFormat: Notion.CollectionViewFormat ): string { const title = escapeString(node.name) // const wrap = (typeof viewFormat.table_wrap === 'undefined') // ? viewFormat.table_wrap : true const viewSchema = (viewFormat.table_properties || []) .filter(colViewInfo => colViewInfo.visible) /** * Some collection views have "ghost" properties that don't exist * in collection schema. */ .filter(colViewInfo => node.schema[colViewInfo.property]) .map(colViewInfo => { const colId = colViewInfo.property return { id: colId, width: colViewInfo.width, name: node.schema[colId].name, type: node.schema[colId].type, options: node.schema[colId].options } }); const theadHTML = viewSchema .map(col => `${escapeString(col.name)}`) const trowsHTML = pages /** Skip empty collection items (pages without properties) */ .filter(page => page.properties) .map(page => { /** * Notion put properties of a collection item in the item's "page" block. * They even mixed those properties with "Notion.BlockProperties". * Quite confusing and messy. */ const pageProps = page.properties as { [key: string]: Notion.StyledString[] } const row = viewSchema.map(clctItemProp => { switch (clctItemProp.type) { case COLLECTION_ITEM_PROPERTY_TYPES.title: { return `${escapeString(page.title)}` } case COLLECTION_ITEM_PROPERTY_TYPES.url: case COLLECTION_ITEM_PROPERTY_TYPES.text: { return `${renderTitle(pageProps[clctItemProp.id])}` } case COLLECTION_ITEM_PROPERTY_TYPES.select: case COLLECTION_ITEM_PROPERTY_TYPES.multiSelect: { const optionNames = pageProps[clctItemProp.id] ? pageProps[clctItemProp.id][0][0].split(',') : [] const optionsHTML = optionNames.map(optionName => { clctItemProp.options = clctItemProp.options as Notion.CollectionColumnOption[] const option = clctItemProp.options.find(o => o.value === optionName) if (!option) { raiseWarning(`Select option "${optionName}" isn't found on property "${clctItemProp.id}:${clctItemProp.name}".`) return '' } const color = `${CSS.tagInTableCellColorPrefix}${option.color}` return `${escapeString(option.value)}` }) return `${optionsHTML.join('')}` } case COLLECTION_ITEM_PROPERTY_TYPES.checkbox: { const checkboxVal = pageProps[clctItemProp.id] const checked = checkboxVal ? checkboxVal[0][0] === 'Yes' : false const checkedSvg = '' const unCheckedSvg = '' if (checked) { return `
${checkedSvg}
` } else { return `
${unCheckedSvg}
` } } default: raiseWarning(`Collection item property type "${clctItemProp.type}" hasn't been implemented.`) return `${renderTitle(pageProps[clctItemProp.id])}` } }) return `${row.join('')}` }) const tableHTML = `\

${title}

${theadHTML.join('')} ${trowsHTML.join('\n')}
` return tableHTML } function renderGallery( node: Nast.Collection, pages: Nast.Page[], viewFormat: Notion.CollectionViewFormat ): string { let title = escapeString(node.name) let imageContain = viewFormat.gallery_cover_aspect ? viewFormat.gallery_cover_aspect === 'contain' : false let pagesHTMLArr = pages.map(page => { return `\
${page.cover ? `` : ''}
${escapeString(page.title)}
` }) let galleryHTML = `\ ` return galleryHTML } function factorySortFunc(sortQuery: Notion.SortQuery[]) { let directions = sortQuery.map(q => { if (q.direction === 'descending') return -1 else return 1 }) return function (a: Nast.Page, b: Nast.Page): number { for (let i = 0; i < directions.length; ++i) { let field: 'title' | 'createdTime' if (sortQuery[i].type === 'title') field = 'title' else if (sortQuery[i].type === 'created_time') field = 'createdTime' else continue /** * Tricks here: when a[field] equals b[field], it does * not return, continue to compare the next field. */ if (a[field] > b[field]) return directions[i] if (a[field] < b[field]) return -(directions[i]) } return 0 } } export default renderCollection